synchronized锁升级过程和实现原理

一.synchronized升级过程

在最开始JDK1.0,1.2的时候,synchronized就是重量级锁,后来JDK对synchronized进行了一系列优化,这个优化就是有个升级过程。这个升级过程被markword清晰地进行了记录。

整个锁升级的过程大概分为:

new-->偏向锁-->轻量级锁(无锁,自旋锁,自适应自旋)-->重量级锁

我们结合下面这张图具体谈一下

从刚刚new出来的时候,首先上的是偏向锁,出现争用,升级为轻量级锁,竞争状态变得特备激烈,变为重量级锁。这些信息全部记录在markword的8个字节中。这张图也说明了在不同的状态下,这64位到底是什么样的布局。在无锁态,头三位代表一个没有锁的状态。如果升级为偏向锁,也是头三位代表它为偏向锁。如果再升级为更重量级的锁,只用两位代表就行了。

刚new出来时,我们看下这64位都对应什么东西?

分代年龄占4位,其最大值即为15。这也就是我们所说的当分代年龄达到最大值时,就从年轻代变为老年代。

hashcode占31位,准确来说,对hashcode方法进行调用之后,它才会把这个值存进来,没有进行调用,这里面是没有值的。

我们看下调用前后内存布局变化:

下面再谈下偏向锁,第一个拥有锁对象的线程,先不上那么重量级的锁,先给它贴个标签, 类比于在卫生间门上贴张写有名字的纸。偏向锁的意思就是它偏向于第一个刚刚进来的线程,因为在竞争不是那么激烈的情况下,其实一个线程拥有这把锁之后,很快就结束了,干完事之后就将这把锁释放了,所以没有必要申请重量级的锁,干脆贴个名字就完事了。所谓的往上贴个标签指的是在markword里面用54位记录了指向当前线程的指针。概括下,从new出来对象到上偏向锁,就是第一个线程来占位的时候,把自己的线程id往上一贴,这坑就属于它了。

当出现第二个线程的时候,就升级为轻量级锁。当B线程来抢坑的时候,发现门上已经贴有A线程的ID了,那A、B两个线程就开始抢坑了,抢坑的过程为:首先撤销偏向锁状态,即把门上标签撕了,每个线程都有自己的线程栈了,在各自的线程栈里生成自己的一个对象,这个对象叫做LockRecord(锁记录)。然后A、B线程开始抢,看谁能把各自LR指针给贴在坑位。也就说如果有任意一个线程抢到了这把轻量级锁的时候,这把轻量级锁里面就记录了指向线程栈中LockRecord的指针,占62位。这个抢的过程是采用自旋的方式,即CAS。用CAS操作把里面62位修改为指向我自己线程栈的LR指针。

竞争加剧后(JDK1.6之前指有线程超过10次自旋,-XX:PreBlockSpin,或者自旋线程数超过CPU核数的一半。JDK1.6之后,加入自适应自旋 Adapative Self Spinning,JVM自己控制。)升级为重量级锁。重量级锁存在于内核态,本质就是mutex数据结构,有数量限制的。用户态想申请重量级锁的时候,得像内核申请。内核给了我这把锁,我才拥有这把锁,这个时候在markword中占62位来记录指向重量级锁的指针。

那么为什么要从轻量级锁升级到重量级锁呢?轻量级锁本质就是执行一个循环,运行在用户态,效率高。但是这个循环是需要不断消耗CPU,如果我们竞争特别激烈,一个线程始终占着,10000个线程都在自旋,那CPU就很快100%了。升级为重量级锁后,维护有一个队列,而在这个队列里,如果没有轮到我执行的时候,是不消耗CPU的,处于wait状态。

下面多提2个概念:

锁消除 Lock eliminate

public void add(String str1, String str2){
    StringBuffer sb = new StringBuffer();
    sb.append(str1).append(str2);
}

我们都知道StringBuffer是线程安全的,因为它的关键方法是被synchronized修饰过的,但我们看上面这段代码,我们就会发现,sb这个引用只会在add方法中使用,不可能被其他线程引用(因为是局部变量,栈私有),因此sb是不可能共享的资源,JVM会自动消除StringBuffer对象内部的锁。因为你不断的append(str1).append(str2).append(str3)......有锁或无锁是没有区别。

锁粗化 Lock coarsening

public String test(String str){
    int i = 0;
    StringBuffer sb = new StringBuffer();
    while(i<100){
        sb.append(str);
        i++;
    }
    return sb.toString();
}

JVM会检测到这样一连串的操作都对同一个对象加锁(while循环内100次执行append,没有锁粗化就要进行100次加锁/解锁),此时JVM就会把加锁的范围放在while外部,使得一连串操作只需要加一次锁即可。 

二.synchonized的实现过程

1.在java代码层面,就是加了一个synchronized关键字

2.在字节码层面,就是加了monitorenter指令和monitorexit指令。进入锁状态和退出锁状态。

3.在JVM执行过程中,自动进行上述锁升级。

4.在汇编层面,就是用了lock comxchg这条指令。

IF_MP 指如果是多个CPU。

至此便是锁的底层实现。

 

 

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 非常感谢您的问题。synchronized锁升级过程Java中锁的优化技术之一,它可以减少锁的竞争,提高程序的执行效率。在Java中,每个对象都有一个锁(也称为内置锁或监视器锁),线程在执行同步代码块时会获取该对象的锁,并在代码块执行完成后释放该锁。当多个线程同时访问同一个对象的同步代码块时,就会出现锁竞争的情况,影响程序的执行效率。 在Java6之前,synchronized锁的实现存在较大的性能问题,当多个线程竞争同一个锁时,会导致线程频繁地进入和退出阻塞状态,增加了线程切换的开销,降低了程序的执行效率。为了解决这个问题,Java6引入了偏向锁、轻量级锁和重量级锁等优化技术,并采用锁升级的方式在不同锁之间进行转换,从而提高程序的执行效率。 具体来说,当一个线程第一次进入同步代码块时,会先尝试获取对象的偏向锁,如果对象没有被其他线程访问过,那么当前线程会获取到该对象的偏向锁,并将当前线程ID记录在锁对象头中,以后该线程每次进入同步代码块时都无需重新获取锁,直接使用偏向锁即可。如果有其他线程访问了该对象,那么当前线程会尝试将偏向锁升级为轻量级锁。轻量级锁是一种自旋锁,它采用CAS(Compare And Swap)指令来保证线程的原子性,避免了线程阻塞和唤醒的开销。如果当前线程获取轻量级锁成功,那么就可以在自旋过程中执行同步代码,避免了线程阻塞和唤醒的开销。如果轻量级锁获取失败,那么当前线程就会尝试将轻量级锁升级为重量级锁,此时当前线程会阻塞等待其他线程释放该对象的锁,直到获取到重量级锁才能执行同步代码。 总之,synchronized锁的升级过程Java中锁的优化技术之一,它可以提高程序的执行效率,减少锁的竞争。 ### 回答2: synchronized锁是Java中用于保证多个线程访问同一个共享资源时的线程安全的关键字。当一个线程想要执行synchronized修饰的同步代码块时,需要先获取对象的锁,如果锁被其他线程占用,则当前线程会被阻塞,直到获取到锁为止。 synchronized锁升级过程分为偏向锁、轻量级锁和重量级锁三个阶段。 首先,在没有竞争的情况下,锁将被优化为偏向锁。偏向锁的作用是,当一个线程获取到锁时,会在锁对象的头部标记记录该线程的ID。在下次该线程再次申请锁时,无需再次进行同步操作,直接获取锁即可。 然后,如果有多个线程竞争同一个锁,偏向锁将转为轻量级锁。这时每个线程都会在自己的线程栈帧中保存锁的副本。线程会通过CAS(比较并交换)来进行锁的获取和释放,而不再阻塞线程。 最后,如果多个线程仍然竞争同一个锁,轻量级锁将升级为重量级锁。重量级锁的实现是利用操作系统提供的互斥量机制,当一个线程获取锁后,其他线程将被阻塞,直到持有锁的线程释放锁。 锁的升级过程在多线程环境下进行,根据锁的状态切换来提高并发效率。通过合理地选择锁的类型以及锁的级别,可以更好地平衡性能与安全性之间的关系。 ### 回答3: synchronized锁升级过程是指在Java中保证多线程访问同步代码时的一种锁优化机制。其主要目的是提高多线程并发访问共享资源时的性能和效率。 当一个线程尝试进入同步代码块时,会先尝试获取对象的无锁状态。如果成功获取无锁状态,则可以直接执行同步代码,并将对象标记为偏向锁。这是锁的第一级别,也是最轻量级的锁。如果在此时另一个线程也想要进入同步代码,就会造成锁竞争。 如果存在锁竞争,偏向锁就会升级为轻量级锁。轻量级锁是通过在对象头中的标识字段中记录指向线程栈中锁记录的指针来实现的。如果线程竞争太激烈,轻量级锁就会升级为重量级锁。 重量级锁是指同步代码块被多个线程访问时,会将线程阻塞并等待锁释放。重量级锁采用操作系统的互斥量实现,所以比较耗时和耗资源。 在锁升级过程中,锁的状态会从无锁状态到偏向锁,再到轻量级锁,最后到重量级锁。在逐步升级的过程中,锁的开销也会逐渐增加。 需要注意的是,在JDK 6之后,引入了锁消除和锁膨胀机制。锁消除指的是JVM在编译器优化时发现某些代码分支中不存在线程竞争时,会去除相应的锁操作;锁膨胀指的是JVM会根据竞争情况,将轻量级锁升级为重量级锁。 综上所述,synchronized锁升级过程是为了提高多线程并发访问同步代码时的性能和效率。通过从无锁状态到偏向锁,再到轻量级锁,最后到重量级锁的升级过程JVM可以根据竞争情况选择最适合的锁状态,以实现最佳的性能和资源利用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值