Java多线程(三)

Java多线程(三)

本章主要讨论synchronized

1.概述

“非线程安全”问题会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取到的数据其实是被更改过的。而“线程安全”就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
synchronzied用来保证线程安全。

2.synchronized的用法

synchronized的用法共有如下四种:

  • (1)synchronized同步方法
  • (2)synchronized(this)同步代码块
  • (3)synchronized(任意对象)同步代码块
  • (4)synchronized同步静态方法

2.1 synchronized同步方法

synchronized同步方法的效果:
1).同一时间只有一个线程可以执行synchronized同步方法中的代码。
2).对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。

public class TestService1 {

    synchronized public void methodA(){
        try {
            System.out.println("methodA: begin! threadName is "+Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("methodA: end! threadName is "+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class RunTest1 {
    public static void main(String[] args) {
        TestService1 testService1 = new TestService1();
        Thread threadA = new Thread(testService1::methodA);
        Thread threadB = new Thread(testService1::methodA);
        threadA.setName("A");
        threadB.setName("B");
        threadA.start();
        threadB.start();
    }
}

运行结果:

methodA: begin! threadName is A
methodA: end! threadName is A
methodA: begin! threadName is B
methodA: end! threadName is B

上述代码及结果说明了第(1)点:同一时间只有一个线程可以执行synchronized同步方法中的代码。
下面将上述代码稍作修改,以验证第(2)点:对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。

public class TestService1 {

    synchronized public void methodA(){
        try {
            System.out.println("methodA: begin! threadName is "+Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("methodA: end! threadName is "+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void methodB(){
        try {
            System.out.println("methodB: begin! threadName is "+Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("methodB: end! threadName is "+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 同步代码块
   public void methodC(){
        try {
            synchronized (this){
                System.out.println("methodC: begin! threadName is "+Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("methodC: end! threadName is "+Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class RunTest1 {
    public static void main(String[] args) {
        TestService1 testService1 = new TestService1();
        Thread threadA = new Thread(testService1::methodA);
        Thread threadB = new Thread(testService1::methodB);
        Thread threadC = new Thread(testService1::methodC);

        threadA.setName("A");
        threadB.setName("B");
        threadC.setName("C");

        threadA.start();
        threadB.start();
        threadC.start();
    }
}

运行结果:

methodA: begin! threadName is A
methodA: end! threadName is A
methodC: begin! threadName is C
methodC: end! threadName is C
methodB: begin! threadName is B
methodB: end! threadName is B

2.2 synchronized(this)同步代码块

synchronized(this)同步代码块的效果:
1).对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
2).同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。
synchronized同步方法或synchronized(this)持有的都是当前对象。
其实2.1节的案例就可以验证synchronized(this)同步代码块的效果,这里不再重复造轮子。

2.3 synchronized(任意对象)同步代码块

1).在多个线程持有“对象监视器”作为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
2).当持有“对象监视器“为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
下面通过代码验证:

public class TestService2 {

    private String threadMonitor = new String();

     public void methodA(){
        try {
            synchronized (threadMonitor){
                System.out.println("methodA: begin! threadName is "+Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("methodA: end! threadName is "+Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class RunTest2 {
    public static void main(String[] args) {
        TestService2 testService2 = new TestService2();
        Thread threadA = new Thread(testService2::methodA);
        Thread threadB = new Thread(testService2::methodA);

        threadA.setName("A");
        threadB.setName("B");
        
        threadA.start();
        threadB.start();

    }
}

运行结果:

methodA: begin! threadName is A
methodA: end! threadName is A
methodA: begin! threadName is B
methodA: end! threadName is B

可以看到threadA和threadB的对象监视器是同一个对象,所以同一时间只有一个线程被执行。
将上述TestService2的代码稍作修改:

public class TestService2 {

//    private String threadMonitor = new String();

     public void methodA(){
         String threadMonitor = new String();
        try {
            synchronized (threadMonitor){
                System.out.println("methodA: begin! threadName is "+Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("methodA: end! threadName is "+Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main方法不变,运行main方法得到结果:

methodA: begin! threadName is A
methodA: begin! threadName is B
methodA: end! threadName is A
methodA: end! threadName is B

此时threadA和threadB的对象监视器不是同一个对象,故异步运行。

2.4 synchronized同步静态方法

关键字synchronized如果应用在static静态方法上,就是对当前的.java文件对应的Class类进行持锁。而synchronized关键字加到非static静态方法上是给对象上锁。

public class TestService3 {

    synchronized public static void methodA() {
        try {
            System.out.println("methodA: begin! threadName is " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("methodA: end! threadName is " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void methodB() {
        try {
            System.out.println("methodB: begin! threadName is " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("methodB: end! threadName is " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class RunTest3 {
    public static void main(String[] args) {
        TestService3 testService3 = new TestService3();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                testService3.methodA();
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                testService3.methodB();
            }
        });

        threadA.setName("A");
        threadB.setName("B");

        threadA.start();
        threadB.start();

    }
}

运行结果:

methodA: begin! threadName is A
methodB: begin! threadName is B
methodA: end! threadName is A
methodB: end! threadName is B 

运行结果是异步的,异步的原因是持有不同的锁,一个是对象锁,另外一个Class锁,Class锁可以对类的所有对象实例起作用。

3.synchronized的特性

synchronized具有以下三个特性:

  • (1)synchronized锁重入;
  • (2)出现异常,锁自动释放;
  • (3)同步不具有继承性。

3.1 synchronized锁重入

关键字synchronized拥有锁重入功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求次对象锁是可以再次得到该对象的锁的。

public class TestService4 {
    synchronized public void methodA(){
        System.out.println("methodA!");
        methodB();
    }
    synchronized public void methodB(){
        System.out.println("methodB!");
        methodC();
    }
    synchronized public void methodC(){
        System.out.println("methodC!");
    }
}
public class RunTest4 {
    public static void main(String[] args) {
        TestService4 testService4 = new TestService4();
        new Thread(testService4::methodA).start();
    }
}

运行结果:

methodA!
methodB!
methodC!

“可重入锁”的概念是:自己可以再次获取自己的内部锁,比如有个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。可重入锁也支持在父子类继承的环境中

3.2 出现异常,锁自动释放

public class TestService5 {
    synchronized public void methodA(){
        if("A".equals(Thread.currentThread().getName())){
            System.out.println("methodA run! threadName is " +Thread.currentThread().getName());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 让A线程运行到这里抛异常
            int a = 5/(2-2);
        }else{
            System.out.println("methodB run! threadName is " +Thread.currentThread().getName());
        }
    }
}
public class RunTest5 {
    public static void main(String[] args) {
        TestService5 testService5 = new TestService5();
        Thread threadA = new Thread(testService5::methodA);
        Thread threadB = new Thread(testService5::methodA);

        threadA.setName("A");
        threadB.setName("B");
        
        threadA.start();
        threadB.start();
    }
}

运行结果:

methodA run! threadName is A
Exception in thread "A" java.lang.ArithmeticException: / by zero
    at com.panda.thread.syntest.test5.TestService5.methodA(TestService5.java:13)
    at java.lang.Thread.run(Thread.java:748)
methodB run! threadName is B

线程A出现异常并释放锁,线程B进入methedA方法正常打印,出现异常的锁被释放了。

3.3 同步不具有继承性

public class TestService6 {
    synchronized public void methodA(){
        try {
            System.out.println("父类TestService6运行方法methodA---begin! theadName is "+Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("父类TestService6运行方法methodA---end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class TestService6Sub extends TestService6{
    @Override
    public void methodA() {
        try {
            System.out.println("子类TestService6Sub运行方法methodA---begin! threadName is "+Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("子类TestService6Sub运行方法methodA---end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        super.methodA();
    }
}
public class RunTest6 {
    public static void main(String[] args) {
        TestService6Sub testService6Sub = new TestService6Sub();
        Thread threadA = new Thread(testService6Sub::methodA);
        Thread threadB = new Thread(testService6Sub::methodA);
        
        threadA.setName("A");
        threadB.setName("B");

        threadA.start();
        threadB.start();
    }
}

运行结果:

子类TestService6Sub运行方法methodA---begin! threadName is A
子类TestService6Sub运行方法methodA---begin! threadName is B
子类TestService6Sub运行方法methodA---end
子类TestService6Sub运行方法methodA---end
父类TestService6运行方法methodA---begin! theadName is A
父类TestService6运行方法methodA---end
父类TestService6运行方法methodA---begin! theadName is B
父类TestService6运行方法methodA---end

从运行结果看出,同步不能继承,所以还得在子类的方法中添加synchronized关键字。
添加关键字synchronized后的运型结果:

子类TestService6Sub运行方法methodA---begin! threadName is A
子类TestService6Sub运行方法methodA---end
父类TestService6运行方法methodA---begin! theadName is A
父类TestService6运行方法methodA---end
子类TestService6Sub运行方法methodA---begin! threadName is B
子类TestService6Sub运行方法methodA---end
父类TestService6运行方法methodA---begin! theadName is B
父类TestService6运行方法methodA---end

4.synchronized和volatile比较

  • (1)关键字volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法、代码块。
  • (2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
  • (3)volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可间接保证可见性,因为他会将私有内存和公共内存中的数据做同步。
  • (4)关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
    关键字volatile主要使用场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,273评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,349评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,709评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,520评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,515评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,158评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,755评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,660评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,203评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,287评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,427评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,122评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,801评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,272评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,393评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,808评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,440评论 2 359