Synchronized 详解

synchronized关键字采用对代码块/方法体加锁的方式解决Java中多线程访问同一个资源时,引起的资源冲突问题。

synchronized 同步锁可分为两种类型,四种表现形式

  • 对象锁: 对单个实例对象的独享内存的部分区域加锁
    • 修饰非静态方法
    • 修饰代码块
  • 类锁: 对整个类的共享内存的部分区域加锁
    • 修饰静态方法
    • 修饰代码块

Find Two Object

沃兹吉索得: 面向对象编程的精髓在于贴合实际,更容易建立易于理解的对象模型。比如设计模式用于面向对象的编程明显更加易于理解。 :)

  1. 首先我们定义一个Human类:
public class Human {
    //所有的Human都有吃晚饭这个动作
    public void eatDinner() {
        pickupByLeftHand(); //用左手拿起食物
        eatFoodByMouth(); //用嘴吃食物
    };
    //所有的Human都有喝水这个动作
    public void drinkWater() {
        pickupByRightHand(); //用右手拿起水
        drinkWaterByMouth();   //用嘴喝水
    } 
    //看电视
    public void watchTV() {
    }
}
  1. 然后我们实例化两个对象:
Human XiaoMing = new Human();  //小明
Human XiaoMingTaBa = new Human();  //小明他爸

Let's have dinner!

小明要吃晚饭了 XiaoMing.eatDinner() , 忽然他又想喝水了 XiaoMing.drinkWater()。好吧,他只有一张嘴,同时又喝水又吃饭会被呛到吧?这时我们想了个解决办法:

//使用 synchronized 关键字修饰 eatDinner() 
public synchronized void eatDinner() {
    pickupByHand(); //用手拿起食物
    eatFoodByMouth(); //用嘴吃食物
}
//使用 synchronized 关键字修饰 drinkWater
public synchronized void eatDinner() {
    pickupByHand(); //用手拿起水
    drinkWaterByMouth();   //用嘴喝水
}

现在当小明在吃饭的时候,他想喝水了,由于我们使用了synchronized修饰这两个动作。小明在喝水之前会先把饭吃完再执行喝水的动作。这样就解决了冲突的问题。
再来分析下watchTV()这个动作:由于这个动作和吃晚饭喝水并没有冲突可以同时进行,所以我们并不需要synchronized关键字来对它进行修饰。

当我们调用synchronized修饰的非静态方法时,所有采用了synchronized修饰的同步方法都会被锁定,所有没有采用synchronized修饰的非同步方法仍然可以执行。

现在我们来探讨一下小明他爸吃饭喝水的问题:虽然我们对小明的eatDinner()方法和drinkWater()方法进行了同步锁定,但是显而易见的是。小明喝水吃饭的动作并不影响小明他爸喝水吃饭的动作。所以我们可以对上面的结论进行完善:

Conclusion1: 使用 synchronized修饰非静态方法
当我们通过某个实例对象A(XiaoMing)访问非静态同步方法(eat or drink)时,所有通过对象A访问非静态同步方法(eat or drink)的操作都会被阻塞直至另一个操作执行完毕,而所有通过对象A访问非静态同步方法的操作(watchTV)仍然可以执行。而当我们通过对象A访问某个同步方法时,仍然可以通过对象B访问任意一个同步方法。(即小明吃饭喝水不影响小明他爸吃饭喝水

Let's have dinner 再来一次!

现在我们来分析另一种吃饭喝水的情况。小明和小明他爸一起在吃饭,两个人都想喝水了,但是桌子上这时只有1瓶水了,这瓶水两个人谁先抢到谁就能喝。小明的经验告诉他吃饭的同时如果喝水的话会被呛到,所以他只好先把饭吃完再去喝水。而小明他爸吃过的盐比小明吃过的米还多, 经验丰富的他知道吃饭时喝水会被呛到是因为我们只有一张嘴,可是我们有两只手啊。所以他的吃饭和喝水的动作是这样实现的:

public void eatDinner() {
    pickupByLeftHand(); //用手拿起食物
    synchronized (mouth) {
        eatFoodByMouth(); //用嘴吃食物
    }
};
public void drinkWater() {
    pickupByRightHand(); //用手拿起水
    synchronized (mouth) {
        drinkWaterByMouth();   //用嘴喝水
    }
}

所以他在吃饭的同时,用手拿起了水。好吧,虽然小明和小明他爸吃饭速度差不多,但是当大家都吃完饭的时候水已经在小明他爸手里了。。。。(小明目瞪口呆.jpg)
总的吃饭时间计时是这样的:(如果不存在竞争关系)
小明:timeOf(用右手拿起食物) + timeOf(用嘴吃食物) + timeOf(用左手拿起水) + timeOf(喝水)
小明他爸:imeOf(用右手拿起食物) + timeOf(用嘴吃食物,同时可以用左手拿起水,而不增加吃食物的时间) + timeOf(喝水)
所以又得到了一个结论

**Conclusion2: **对象锁的两种形式的区别
这两种形式没有本质上的不同。区别在于修饰代码块的形式尽可能的精简了需要锁住的同步代码,使的我们的系统在高并发、资源竞争激烈的情况下更加高效。

关于类锁

好吧,我想不出类比的例子了。直接说结论

Conclusion3: 类锁(synchronized修饰静态方法,synchronized(Human.class)修饰代码块)
访问这两种方式修饰的操作时,所有其他的采用synchronized修饰的静态方法和采用synchronized(Human.this)修饰的代码块都会被锁定(静态的非同步方法不会被锁定)。但是不会影响到对象锁,文章开头有说明两种锁锁住的内存区域是不同的!

Conclusion4: 类锁的两种形式的区别
参考对象锁两种形式的区别

The Last Supper

关于synchronized的两种类型,四种形式我们可以得出以下结论

  1. 当一个线程通过A对象访问一个非静态的同步方法(或同步代码块)时,其他线程通过A对象访问非静态同步方法(或同步代码块)的操作都会被锁定。但是其他线程通过A对象访问非静态非同步方法的操作不会被锁定,而且通过B对象访问非静态同步方法的操作也不会被锁。(因为各个对象的锁是独立的所以叫做对象锁)
  2. 当一个线程访问静态同步方法或者由类锁修饰的同步代码块时,所有该类的静态方法或者被同一个类锁修饰的同步代码块都会被锁定,其他线程无法访问。
  3. 类锁和对象锁互不影响。
  4. 修饰方法和修饰代码块在本质上是一样的,区别只在于修饰代码块的方式在竞争激烈的情况下更加高效。
  5. 注意区别以下代码的对象锁的持有情况
public class Human {
    //XiaoMing.eatDinner();
    public synchronized void eatDinner() {   //这里的锁是XiaoMing
        pickupByLeftHand();
        eatFoodByMouth();
    };
    
    //XiaoMing.eatDinner2();
    public void eatDinner() {
        synchronized(this) {    //这里的锁是XiaoMing
            pickupByLeftHand();
            eatFoodByMouth();
        }
    };
    
    //XiaoMing.drinkWater();
    public void drinkWater() {
        pickupByRightHand();
        synchronized (mouth) {  //这里的锁是mouth
            drinkWaterByMouth();
        }
    }
}

附:四种形式的锁

  • 对象锁: 对单个实例对象的独享内存的部分区域加锁
    • 修饰非静态方法
public synchronized void doSth() {}
  • 修饰代码块
public void doSth() {
      synchronized (obj) {
      }
}
  • 类锁: 对整个类的共享内存的部分区域加锁
    • 修饰静态方法
public static synchronized void doSth() {}
  • 修饰代码块
public void doSth() {
      synchronized (ClassA.class) {
      }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容

  • 一、概念 synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的。 锁机制有如下两种特性:...
    zly394阅读 11,019评论 1 6
  • 1、了解synchronized synchronized是Java中的关键字,是一种同步锁。当多个并发线程访问同...
    黒猫阅读 1,610评论 0 2
  • 在java多线程并发编程中对于 sychronized 大家一定不陌生,同步关键字 synchronized 是j...
    杨文杰阅读 3,032评论 0 12
  • 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门...
    DanieX阅读 16,349评论 7 16
  • 什么是Synchronized,有什么用? Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保...
    phoenixsky阅读 1,425评论 0 0