Synchronized 学习

等待-通知机制:

类比现实世界中的就医流程:

1. 我们先去挂号,然后到就诊门口,排队等待叫号
2. 当我们的号码被叫时,我们进门诊看医生
3. 医生说我们需要验血,于是我们去抽血验血,他叫下一位排队的病人
4. 我们拿到验血报告后,重新分诊后在门诊口排队,等待叫号
5. 当医生再次叫到我们的号时,我们再进去看医生。
6. 看完后,我们就回家。

就医流程结合并发编程会是什么样的那?

1. 我们到门诊就诊,类似于线程去获取互斥锁,在门诊口排队等待叫号,类似于互斥锁被其他线程占有;
2. 我们进门诊看医生,类似于线程获取到了互斥锁;
3. 医生需要验血报告,让我们去抽血,类似于线程要求的条件没有满足(已经获取锁了)
4. 我们去抽血,类似于线程进入等待状态,医生叫下一个病人,类似于线程释放了持有的互斥锁
5. 我们拿到验血报告,类似于线程要求条件得到满足;我们到门诊重新分诊,类似于线程重新去获取互斥锁。

总结:线程首先去获取互斥锁,如果锁被其他线程占有,则排队等待,当线程获取到锁后,进入临界区。如果线程要求的条件不满足时,释放锁并进入等待状态;当要求的条件满足时,通知等待的线程,去获取互斥锁。这就是完整的等待-通知机制,也就是wait-notify机制。

Java中的等待-通知机制

synchronized配合wait(),notify()

````
Object lock = new Object();
int count = 0;
boolean condition = false;
public void test(){
    synchronized(lock){
        while(!condition){
            lock.wait()
        }
        lock.notify();
        count++;
    }
}
public void changeCondition(){
    condition = true;
}
````
代码分析:synchronized可以保护临界区的代码在同一时刻只被一个线程访问,它是Java内置的互斥锁。当线程必须等待,某一条件成立时,才能访问共享资源,必须在 synchronized{}内部调用 wait() 方法,释放占有的锁,并进入等待状态,阻塞线程。当条件满足时,调用 notifyAll() 唤醒等待队列中所有等待状态的线程,被唤醒的线程进入就绪状态,开始竞争互斥锁。
注意:

wait()和sleep()的区别:1. wait()释放线程持有的锁,sleep()不会;2. wait()必须在 synchronized{} 保护的临界区内调用,sleep则没有限制;3. wait不能自动唤醒线程,必须通过 notify,sleep则在指定的时间后自动唤醒;4. wait() 是 Object的方法,sleep() 是 Thread 的方法。
notify()和notifyAll()的区别:notify只能随机唤醒等待队列中的一个线程,natifyAll能唤醒等待队列中所有的线程。唤醒的线程不一定能立即获取锁,它需要先改变等待状态,进入就绪状态,才能开始竞争锁。

Synchronized

为什么 synchronized(lock){临界区} 就可以实现临界区代码互斥的功能?

synchronized 关键字的底层实现是两个指令: monitorentermonitorexit,而lock只是一个普通的 java 对象。

Java对象在内存长什么样子?

Java对象在内存中的分布为:对象头,实例数据,对齐方式。其中,对象头描述了对象运行过程中的信息,如:锁状态(无锁,偏向锁,轻量级锁,重量级锁),哈希值,GC分代年龄,偏向锁标志,锁标志位,计数器,当前线程指针...
当我们使用 new 关键字创建一个对象时,JVM会在堆中创建一个 instanceOopDesc 对象,这个对象包含了对象头(MarkWord)和实例数据。

MarkWord 的存储结构图

注意:在Java6之前,并没有轻量级锁和偏向锁,只有重量级锁,也就是我们常用的 synchronized的对象锁 。Java6之后,对 synchronized 进行了优化,添加了轻量级锁,偏向锁,以及锁自旋。目的是:避免 ObjectMonitor 的访问,减少"重量级锁"的使用,并最终减少线程上下文切换的频率。

图中,当锁的标志位为 10 时,锁的状态是重量级锁,对象头中用 30bit 来指向一个互斥量 Monitor。

什么是 Monitor ?怎么创建的?

Monitor 可以理解为一个同步工具,也可以描述为同步机制,它是保存在 MarkWorld 中的一个对象。通过下面方法创建:

bool has_monitor() const {
        return ((value) $monitor_value)!=0);
    }
    ObjectMonitor* monitor() const {
        assert(has_monitor(),"check");
        return (ObjectMonitor*) (value() ^ monitor_value);
}

上述代码告诉我们,Monitor其实是 ObejctMonitor 类型的实例对象。而且 Java中每个对象都会有一个对应的 ObjectMonitor 对象,所以,Java 中的所有 Object 都可以作为锁对象。这也解释了为什么 Obejct 中会有 wait 和 notify 方法。

ObjectMonitor如何实现同步机制那?

ObjectMonitor(){
    _count = 0; // 记录该线程获取锁的次数
    _owner = NULL; // 持有对象锁的线程
    _WaitSet = NULL; // 存储处于wait状态的线程的队列
    _EntryList = NULL; // 存储处于block状态(等待锁)的线程的队列
    _recursions = 0; //记录锁的重入次数
    ....
}
  1. 当多个线程同时访问同步代码时,会先进入 _EntryList 线程队列中;
  2. 当某个线程通过竞争获取到对象锁后,_owner 设置为当前线程,_count 加1;_EntryList 中的线程进入阻塞状态(blocking)
  3. 当持有对象锁的线程,遇到 wait 方法后,该线程释放锁,_owner 重置为 NULL,_count减1,同时该线程进入 _WaitSet 中等待被唤醒,当前线程处于等待状态(wait);同时,_EntryList中阻塞状态的线程开始竞争锁对象。假设其中一个获取到锁,又会走第 2 步;
  4. 当持有对象锁的线程,调用 notify 时,处于_WaitSet集合中的线程被唤醒,加入_ENtryList队列中,同时将状态改为阻塞状态。注意:此时,当前线程依然持有锁。

总结:上诉过程,每次获取锁,释放锁,都会阻塞和唤醒线程,而线程切换需要CPU从用户态转入内核态,性能消耗比较严重。所以,JVM使用 自旋锁,偏向锁,轻量级锁,优化 synchronized。

synchronized修饰普通方法,静态方法和代码段氏,有什么区别?

  1. 修饰静态方法时,锁对象是当前类的 Class 对象,而 Class 对象在程序运行过程中只有一个,所以,访问该方法时会自动加锁与解锁,即执行 monitorentermonitorexit
  2. 修饰普通方法时,锁对象是当前类的 this 对象,而 this 对象是通过 new 关键字创建在堆内存中的,不同的对象就代表不同的锁。而且该方法会被标记为 ACC_SYNCHRONIZED,自动在调用与退出时执行 monitorentermonitorexit

参考自

  1. https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1863
  2. https://mp.weixin.qq.com/s/fyvoraVu9yjgqX-xhn6EHQ
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容