synchronized

synchronized

synchroized 是一个重量级锁,但是现在经过优化后,引入了自旋锁、锁消除、锁粗化、偏向锁、轻量级锁、重量级锁后已经不是很笨重了。

synchroized实现原理

底层是使用monitorenter 和 monitorexit 指令实现的。

public class Demo {

    ...

    public void add() {
        synchronized (this) {
            index++;
        }
    }
    ...
}
 public void add();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter
       4: aload_0
       5: getfield      #2                  // Field index:Ljava/lang/Integer;
       8: astore_2
       9: aload_0
      10: aload_0
      11: getfield      #2                  // Field index:Ljava/lang/Integer;
      14: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
      17: iconst_1
      18: iadd
      19: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      22: dup_x1
      23: putfield      #2                  // Field index:Ljava/lang/Integer;
      26: astore_3
      27: aload_2
      28: pop
      29: aload_1
      30: monitorexit
      31: goto          41
      34: astore        4
      36: aload_1
      37: monitorexit
      38: aload         4
      40: athrow
      41: return

从上面可以看到,monitorenter 会插入到同步代码块开始的位置,monitorexit 会插入到同步代码块结束的位置,JVM保证每一个monitorenter都有一个monitorexit对应。

Java对象头

sychronized是存放在Java 对象头中的,Java对象头分为两个部分,分别是mark word 与 klass poniter,klass poniter 存放的是对象指向类元数据的指针,JVM通过这个知道该对象是哪个类的实例。mark word 存放的是 哈希码、GC年龄分代、锁状态标志位、线程持有锁、偏向锁ID等信息。

MarkWord.png(参考Java并发编程艺术)

自旋锁、适应性自旋锁

由于线程阻塞和唤醒给系统的性能会带来很大的影响,而且很多时候,对象被占有其实是很短的一段时间,为了很短的一段时间,频发阻塞唤醒就很不值当了,所以引入了自旋锁。

自旋锁:

就是线程不会发现对象被其他线程持有的时候,该线程并不会立即挂起,而是执行一段无意义的循环,但是这种循环也会给CPU带来压力,所有就限制循环的次数,但是有一个问题就是,假设设置自旋的次数为10次,执行完10后并没有获得,所以挂起,假设在第11次或第12次获得,这个就很尴尬了,所以引入了适应性自旋锁。

适应性自旋锁:

旋转的次数不在固定,而是由它前一次获取该锁的自旋时间及锁的状态决定。

  • 线程如果自旋成功,获得锁了,那么下一次的自旋此时就会增加,JVM会认为上次已经成了,那么这次成功的可能性也会很大
  • 如果很少成功或没有成功,JVM会减少自旋的次数或不进行自旋。

锁消除

利用逃逸分析来确定是否进行锁消除,也就是当JVM检测到不可能存在共享数据的竞争的时候,也就没有的加锁的意义。

  /**
  * 在StringAppend方法中不可能存在共享数据竞争的时候
  */
public class Demo {
    public String StringAppend(){
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("1");
        stringBuffer.append("2");
        return stringBuffer.toString();
    }
}

锁粗化

将多个连续加锁、解锁的操作连在一起后,扩大成一个更大的锁。
例如在一个for循环,Vector 的add操作就会频繁的加锁、解锁,这个时候就适合锁粗化。

public class Demo {

    private Vector<Integer> vector = new Vector<>();

    public void add() {
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
    }
}

锁升级

锁有四种状态:无锁状态、偏向锁、轻量级锁、重量级锁,根据竞争锁的程度依次升级,只允许升级,不允许降级。

偏向锁

核心思想:

如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也就变成偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word锁标志是否为偏向锁已经当前线程ID等于Mark Word的ThreadId即可。

基于Java对象头实现的

目的: 在无线程的竞争下减少不必要的获取轻量级锁的执行

获取过程:

  1. 检查对象头的mark word 是否可为偏向的状态。就是判断mark word 里面的是否为偏向锁的值是否为1,锁标志位的值是否为01.

  2. 若锁标志位为01,则判断对象头中的线程ID是否为当前线程ID。如果是,执行同步代码块。

  3. 若对象头中的线程ID不是当前ID,则通过CAS替换对象头中的线程ID,替换成功,执行同步代码块

  4. 若没有替换成功,表明有多个线程竞争,当到达全局安全点时,获取到偏向锁的线程挂起,偏向锁升级为轻量级锁,被挂起在全局安全点的线程继续执行。

偏向锁的释放:

线程不会主动释放偏向锁,而是采用了多个线程竞争时,释放偏向锁。偏向锁的释放需要等待全局安全点

全局安全点

在这个时间点上没有执行的代码。

轻量级锁

基于Java对象头实现的
当关闭偏向锁或偏向锁升级的时候,会升级为轻量级锁

适应的场景:线程交替执行同步块

获取过程:

  1. 判断当前对象是否是无锁状态?如果是,则JVM首先将当前现在的栈帧中,建立一个名字为锁记录(Lock Record)的空间,用于存储锁对象目前的mark word 的拷贝,即 Displaced Mark Word。如果不是,执行步骤 3。
  2. JVM利用CAS操作将对象头对的mark word 更新为指向Lock Record,如果成功,表示竞争到锁,将锁的标志位改为00(表示轻量级锁)执行同步操作。如果失败,执行步骤 3。
  3. 判断对象的mark word 是否之前当前线程的栈帧,如果是,表示已经持有当前对象的锁,执行同步代码块,如果不是,说明当前对象已经被其他对象持有,则当前线程尝试自旋来获取锁,如果没有获取到,则将锁的标识为改为10,表示锁膨胀成重量级锁,当前线程阻塞。

轻量级锁的释放

也是通过CAS进行的释放。

多线程竞争的情况下,轻量级锁的效率比重量级效率低,因为还要又一个CAS的操作

重量级锁

通过对象内部监视器,monitor 实现的。

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

推荐阅读更多精彩内容