首页 技术 正文
技术 2022年11月18日
0 收藏 735 点赞 3,179 浏览 7028 个字

目录

1.为什么要使用synchronized?

在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性)

2.synchronized锁什么,加锁的目的是什么?

synchronized锁的是对象,锁的的对象可能是this、临界资源对象、class类对象
加锁的目的是保证操作的原子性

3.代码示例

3.1锁this和临界资源对象

本例中:

  • testSync1是synchronized代码块,锁的是object,临界资源对象
  • testSync2和testSync3都是锁的是当前对象this
/** * synchronized关键字 * 锁对象。synchronized(this)和synchronized方法都是锁当前对象。 */package com.bernardlowe.concurrent.t01;import java.util.concurrent.TimeUnit;public class Test_01 {    private int count = 0;    private Object object = new Object();    // 1.synchronized代码块,锁的是object,临界资源对象    public void testSync1(){        synchronized(object){            System.out.println(Thread.currentThread().getName()                    + " count = " + count++);        }    }    // 2.synchronized代码块,锁的是当前对象this    public void testSync2(){        synchronized(this){            System.out.println(Thread.currentThread().getName()                    + " count = " + count++);        }    }    // 2.synchronized代码,锁的也是this    public synchronized void testSync3(){        System.out.println(Thread.currentThread().getName()                + " count = " + count++);        try {            TimeUnit.SECONDS.sleep(3);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public static void main(String[] args) {        final Test_01 t = new Test_01();        new Thread(new Runnable() {            @Override            public void run() {                t.testSync3();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                t.testSync3();            }        }).start();    }}

3.2锁class类对象

/** * synchronized关键字 * 同步方法 - static * 静态同步方法,锁的是当前类型的类对象。在本代码中就是Test_02.class */package com.bernardlowe.concurrent.t01;import java.util.concurrent.TimeUnit;public class Test_02 {    private static int staticCount = 0;    public static synchronized void testSync4(){        System.out.println(Thread.currentThread().getName()                + " staticCount = " + staticCount++);        try {            TimeUnit.SECONDS.sleep(3);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }    public static void testSync5(){        synchronized(Test_02.class){            System.out.println(Thread.currentThread().getName()                    + " staticCount = " + staticCount++);        }    }}

3.3 什么时候锁临界资源,什么时候锁当前对象?

如果在加锁的时候需要对当前对象的访问限定,建议锁临界资源(即锁一个临界资源),如果对当前锁级别比较高的话,就锁当前对象。建议都以同步代码块的方式进行开发,这样可以避免锁的范围太高

同步方法和非同步方法
提问:同步方法是否影响其他线程调用非同步方法,或调用其他锁资源的同步方法?

代码示例
m1是非同步方法,m2是同步方法,m3同步代码块,锁的临界资源,这段代码的目的是为了证明在调用同步方法m1时,m2,m3是否能够执行

/** * synchronized关键字 * 同步方法 - 同步方法和非同步方法的调用 * 同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。 */package com.bernardlowe.concurrent.t01;public class Test_04 {    Object o = new Object();    public synchronized void m1(){ // 重量级的访问操作。        System.out.println("public synchronized void m1() start");        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("public synchronized void m1() end");    }    public void m3(){        synchronized(o){            System.out.println("public void m3() start");            try {                Thread.sleep(1500);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("public void m3() end");        }    }    public void m2(){        System.out.println("public void m2() start");        try {            Thread.sleep(1500);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("public void m2() end");    }    public static class MyThread01 implements Runnable{        public MyThread01(int i, Test_04 t){            this.i = i;            this.t = t;        }        int i ;        Test_04 t;        public void run(){            if(i == 0){                t.m1();            }else if (i > 0){                t.m2();            }else {                t.m3();            }        }    }    public static void main(String[] args) {        Test_04 t = new Test_04();        new Thread(new MyThread01(0, t)).start();        new Thread(new MyThread01(1, t)).start();        new Thread(new MyThread01(-1, t)).start();    }}

执行结果
多线程总结-同步之synchronized关键字
可以看出,在执行同步方法m1时,m2,m3不受影响,依然可以执行
结论:同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。
但有一点需要注意,尽量在商业开发中避免同步方法。使用同步代码块。 细粒度解决同步问题。

3.3 重入锁

这里重入锁分为两类:

  • 1.在同步方法里面调用其他同步方法
  • 2.子类同步方法覆盖父类同步方法

下面来看第一种:在同步方法里面调用其他同步方法
思考:调用m1()方法,m2()方法是否会执行?

/** *synchronized关键字 *同步方法 - 调用其他同步方法 */package com.bernardlowe.concurrent.t01;import java.util.concurrent.TimeUnit;public class Test_06 {    synchronized void m1(){ // 锁this        System.out.println("m1 start");        try {            TimeUnit.SECONDS.sleep(2);        } catch (InterruptedException e) {            e.printStackTrace();        }        m2();        System.out.println("m1 end");    }    synchronized void m2(){ // 锁this        System.out.println("m2 start");        try {            TimeUnit.SECONDS.sleep(1);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("m2 end");    }    public static void main(String[] args) {        new Test_06().m1();    }}  

多线程总结-同步之synchronized关键字
结果:在m1()方法调用时,m2()方法依然可执行。多次调用同步代码,锁定同一个锁对象,是可重入的

第二种情况:子类同步方法覆盖父类同步方法
思考:子类同步方法m()中,调用父类同步方法m(),是否可重入?

/** * synchronized关键字 * 同步方法 - 继承 * 子类同步方法覆盖父类同步方法。可以指定调用父类的同步方法。 * 相当于锁的重入。 */package com.bernardlowe.concurrent.t01;import java.util.concurrent.TimeUnit;public class Test_07 {    synchronized void m(){        System.out.println("Super Class m start");        try {            TimeUnit.SECONDS.sleep(1);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Super Class m end");    }    public static void main(String[] args) {        new Sub_Test_07().m();    }}class Sub_Test_07 extends Test_07{    synchronized void m(){        System.out.println("Sub Class m start");        super.m();        System.out.println("Sub Class m end");    }}

多线程总结-同步之synchronized关键字
结果:子类同步方法m()中,调用了父类同步方法m(),可以重入

3.4 锁与异常

思考:当同步方法或同步代码块中发生异常,是否会影响其他线程的执行?
下面来看一段代码

/** * synchronized关键字 * 同步方法 - 锁与异常 */package com.bernardlowe.concurrent.t01;import java.util.concurrent.TimeUnit;public class Test_08 {    int i = 0;    synchronized void m(){        System.out.println(Thread.currentThread().getName() + " - start");        while(true){            i++;            System.out.println(Thread.currentThread().getName() + " - " + i);            try {                TimeUnit.SECONDS.sleep(1);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            // 当i==5时会抛出异常            if(i == 5){                i = 1/0;            }        }    }    public static void main(String[] args) {        final Test_08 t = new Test_08();        new Thread(new Runnable() {            @Override            public void run() {                t.m();            }        }, "t1").start();        new Thread(new Runnable() {            @Override            public void run() {                t.m();            }        }, "t2").start();    }}

这段代码中先运行了两个线程t1、t2,当其中一个线程发生异常时,另外一个线程是否能继续执行
多线程总结-同步之synchronized关键字
结果:当同步方法中发生异常的时候,自动释放锁资源。不会影响其他线程的执行。

思考: 同步业务逻辑中,如果发生异常如何处理?
比如上面会发生异常的代码中,可以这样

            if(i == 5){                try {                    i = 1/0;                } catch (Exception e) {                    i = 0;                }            }

3.5 锁对象变更问题

代码示例:
思考:当一个线程执行同步方法时,另一个线程修改了锁对象,是否还能执行同步代码块

/** * synchronized关键字 * 锁对象变更问题 */package com.bernardlowe.concurrent.t01;import java.util.concurrent.TimeUnit;public class Test_13 {    Object o = new Object();    void m(){        System.out.println(Thread.currentThread().getName() + " start");        synchronized (o) {            while(true){                try {                    TimeUnit.SECONDS.sleep(1);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(Thread.currentThread().getName() + " - " + o);            }        }    }    public static void main(String[] args) {        final Test_13 t = new Test_13();        new Thread(new Runnable() {            @Override            public void run() {                t.m();            }        }, "thread1").start();        try {            TimeUnit.SECONDS.sleep(3);        } catch (InterruptedException e) {            e.printStackTrace();        }        Thread thread2 = new Thread(new Runnable() {            @Override            public void run() {                t.m();            }        }, "thread2");        t.o = new Object();        thread2.start();    }}

多线程总结-同步之synchronized关键字
结论:可以看出,其他线程依然可以执行同步方法
因为同步代码一旦加锁后,那么会有一个临时的锁引用执行锁对象,和真实的引用无直接关联。在锁未释放之前,修改锁对象引用,不会影响同步代码的执行。

注意:不要使用静态常量作为锁对象
如下代码,因为String常量池的问题,s1,s1是同一个对象,所以m1,m2方法锁的是也同一个对象,m1同步方法被执行后,m2方法不会被执行

/** * synchronized关键字 * 常量问题 */package com.bernardlowe.concurrent.t01;public class Test_14 {    String s1 = "hello";    String s2 = "hello";    // String s2 = new String("hello"); // new关键字,一定是在堆中创建一个新的对象。    void m1(){        synchronized (s1) {            System.out.println("m1()");            while(true){            }        }    }    void m2(){        synchronized (s2) {            System.out.println("m2()");            while(true){            }        }    }    public static void main(String[] args) {        final Test_14 t = new Test_14();        new Thread(new Runnable() {            @Override            public void run() {                t.m1();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                t.m2();            }        }).start();    }}
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,000
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,512
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,358
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,141
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,771
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,849