java多线程之二——Synchronized

在java多线程并发编程中,Synchronized一直占有很重要的角色。Synchronized通过获取锁来实现同步。先来看一下,它的使用方法:

package com.Vinctor.Tst;

public class VinctorSyncDemo {

   public static synchronized void staticSyncMethod() {
       System.out.println("static synchronized");
   }

   public synchronized void normalSyncMethod() {
       System.out.println("normal  SyncMethod");
   }

   public void normalInnerMethod() {
       synchronized (this) {
           
       }
   }

   public void staticInnerMethod() {
       synchronized (VinctorSyncDemo.class) {

       }
   }
}

如上,分为三种方式:

  • staticSyncMethod,修饰静态方法,锁是当前类的类对象,位于方法区
  • normalSyncMethod,修饰普通实例方法,锁是当前实例对象,位于堆
  • 修饰代码块,如normalInnerMethodstaticInnerMethod,其中synchronized (this)normalSyncMethod效果相同,synchronized (VinctorSyncDemo.class)staticSyncMethod效果相同。

我们将上述代码javap之后,看一下字节码指令:

image.png

可以看到看到staticInnerMetho方法在执行到synchronized代码块时,有两个指令monitorentermonitorexit,而下面异常表指向的位置10,也同样执行了monitorexit。可见同步代码块的实现是使用monitorentermonitorexit两个代码块进行获取锁已释放锁的,发生异常之后,也同样会释放锁。JVM 必须保证monitorentermonitorexit相对应。当监视器一个线程被持有时,那么这个线程就持有了锁,其他线程就不能获取这个监视器,直至monitorexit释放锁。同步方法同样也可以用着两个指令获取和释放锁。

当一个线程试图访问同步方法或者同步代码块时,首先需要获取锁,退出同步方法或者同步代码块时需要释放锁。可以看出锁在Synchronized中起到至关重要的作用。锁是什么呢?他是怎么存储的呢?
通过以前的文章,我们了解到实例对象存储在堆中,类对象存储在方法中,一个对象区域包括:对象头,对象数据,对其数据。此处对象头中存储着Synchronized的锁。对象头中有一个称为Mark Word的区域,存储着对象的 hashCode,分代年龄和锁标记位。如图:

image.png

运行期间,mark word 中存储的数据锁的标记位的变化而变化,其可能变化情况如下:

image.png

可以看到,分为很多锁:偏向锁,轻量级锁,重量级锁。

锁的分类

内置锁按照状态分为:无锁状态,偏向锁,轻量级锁,重量级锁。四中状态随着锁的竞争加剧而升级,但是不是回退降级。(本文仅讨论 HotSpot)

锁的升级

虚拟机开发人员研究发现,大多数情况下,多线程存在竞争的情况很少,为了避免同一线程多次进行锁的获取,故引入了偏向锁的概念。

当一个线程A访问同步代码块的时候,
首先检查锁标志位:如果为01(无锁或偏向锁),再检查是否为偏向锁,

  • 如果为0(不是偏向锁),会通过 CAS 操作(下面将解释)在对象头中存储当前访问的线程 A 的ID ;
  • 如果为1(是偏向锁),检查一下对象头中是否是当前线程A 的 ID,如果是,则表示获取到锁,执行代码块。接下文,
    接上文,如果不是当前线程A 的 ID,则尝试使用 CAS替换线程 ID,如果成功,则表示获取锁;如果失败,则表示其他线程正在持有锁,这时出现了竞争。我们假设当前线程 B 持有该锁。出现了竞争,我们这时需要撤销线程 B 的偏向锁(解锁)。
    当线程 B 运行到安全点或者安全区域的时候,线程 B 暂停,这是检查线程 B是否已经退出了同步代码块,
  • 如果线程 B已经退出了同步代码块,则解锁,将对象头 的线程 ID 清空,是否偏向锁标记位0(设为无锁状态),线程 A 继续通过 CAS 获取锁。
  • 如果线程 B 没有退出同步代码块,则表示线程 A 不能获取锁,这时锁升级,升级为轻量级锁

锁升级轻量级锁之后,对象头中不在存储线程 ID 等信息,而是将这些信息拷贝至持有锁的线程栈中锁记录中,再将对象头指向该地址。上面的例子🌰中,

  • 线程 B 持有锁,在线程 B 的栈中分配锁记录,并将对象头数据拷贝进去,这时锁的标志位为00(轻量级锁),并将对象头指针指向该线程 B 的栈,线程 B 唤醒,并继续执行代码;
  • 此时线程 A 也是分配锁记录,并拷贝对象头中 Mark Word,但是线程 A 还是不能获取到锁。
    这时线程 A还是进行 CAS 操作,企图将对象头指向自己的锁记录,
  • 如果替换成功,则表示线程 A 获取到了锁,执行同步代码块;
  • 如果还是不成功,这时线程 A 将执行自旋CAS(自旋:顾名思义,自己转着玩儿,也就是线程 A 不暂停等待,也不阻塞,而是执行一些无用的代码,此时空转,也占用 CPU 时间,可以想象一个while 循环,目的就是稍等一下,看看持有锁的线程是是否很快就释放锁),自旋的过程中尝试 CAS 替换对象头指针,当自旋一定数目之后,线程 A 还是没有获取到锁(够悲催的),这时锁升级,升级为重量级锁,此时标志位10。升级为重量级锁之后,线程 A 这是不在争抢资源,而是挂起当前线程,等待其他线程释放锁之后将它唤醒。

拥有轻量级锁的线程执行完同步代码块后,需要解锁轻量级锁,这是还是需要使用 CAS 操作,将栈中锁记录替换会对象头,如果成功,表示没有竞争;如果失败,表示存在竞争,其他线程尝试获取过锁,那就需要在释放锁的过程中,唤醒被挂起的线程。

贴一张收藏的流程图(出处不明,如侵权,请告知):


点击可放大,可查看原图

一个🌰

以上就是锁升级的过程,比较乱,举个现实生活的栗子,上厕所。厕所几位锁(对象)。

为了方便对比,我们定一个规则,当一个人上厕所时,需要将自己的牌子挂在厕所的门上,上完厕所,从厕所出来就不必摘下牌子,下次再上的时候就不需要挂了,只是看一下这个牌子是不是自己的就可以了。此为偏向锁。

当你再上厕所的过程中,小明过来了也想上厕所,就看到了牌子,不是自己的,想要把牌子换成自己的,但是这是你还在上厕所,没办法,小明只能在厕所门前晃来晃去,等着你上完厕所出来。如果你能在短时间里出来,那小明就进去上厕所了。此时为轻量级锁。

但是万一你闹肚子,小明也不可能一直在外面等着,这是他就选择回座位等着,并告诉你一声:“哥们,上完告诉我一声!”,这时的小明不在主动去想要获取锁,而是等着你上完厕所出来喊他,他才上厕所。这是即为重量级锁。

CAS

CAS,Compare and Swap即比较并替换,设计并发算法时常用到的一种技术。java 中的原子类以及concurrent包大量使用了该技术进行原子操作。

CAS有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false

JDK中有一个类Unsafe(sun.misc.Unsafe),它提供了硬件级别的原子操作。Unsafe类中的一个方法如下:

public final native boolean compareAndSwapInt(Object o, long offset,
                                              int expected,
                                              int x);

此为 native 方法,JVM 会将此方法映射为cmpxchgCPU 指令,该指令为原子操作,故多用于多线程环境中而不会产生数据错误。

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

推荐阅读更多精彩内容