并发编程之synchronized(二)------jvm对synchronized的优化

1、锁的粗化

看以下代码java

public class Test {
    StringBuffer stb = new StringBuffer();


    public void test1(){
        //jvm的优化,锁的粗化
        stb.append("1");

        stb.append("2");

        stb.append("3");

        stb.append("4");
    }

首先咱们要清除StringBuffer是线程安全的,由于它在每个方法上都加了synchronized锁,下图是StringBuffer的源码安全

 

 按照正常的理解synchronized是对当前对象加锁,那么咱们调用了四次append方法,那么jvm是将这把对象锁加了四次吗?以下图:多线程

 

 那这样的化,jvm就须要加四次锁,固然也要释放四次锁,频繁加解锁引发线程上下文的切换,很是消耗性能,因此jvm作了优化,只加一次锁,叫作锁的粗化,能够理解为将锁的颗粒度放大app

 2、锁的消除

如图看下面代码jvm

 public void test2(){
        //jvm的优化,JVM不会对同步块进行加锁
        synchronized (new Object()) {
            //伪代码:不少逻辑
            //jvm是否会加锁?
            //jvm会进行逃逸分析
        }
    }

这个地方加锁等于没有加锁,由于每一个线程都会new object,你们都不会用同一把锁,jvm分析优化后不会对这种代码加锁(逃逸分析),因此,咱们平时加锁必定要注意,加锁要加同一把锁。性能

3、锁的膨胀升级

一、锁的升级

synchronized的锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁能够从偏向锁升级到轻量级锁,再升级的重量级锁,可是锁的升级是单向的,也就是说只能从低到高升级,锁状态的升级不可逆。优化

 

 

 

JDK1.6版本以后对synchronized的实现进行了各类优化,如自旋锁、偏向锁和轻量级锁 并默认开启偏向锁 开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 关闭偏向锁:-XX:-UseBiasedLocking。若是直接上来就是重量级锁,那么实在是太消耗资源了。spa

二、锁的状态记录在哪里

HotSpot虚拟机的对象头包括两部分信息,第一部分是“Mark Word”,用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。

 

 三、对锁的理解:

(1)、偏向锁,偏向锁是Java 6以后加入的新锁,它是一种针对加锁操做的优化手段,通过研究发现,在大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,所以为了减小同一线程获取锁(会涉及到一些CAS操做,耗时)的代价而引入偏向锁。偏向锁的核心思想是,若是一个线程得到了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再作任何同操做,即获取锁的过程,这样就省去了大量有关锁申请的操做,从而也就提供程序的性能。因此,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续屡次是同一个线程申请相同的锁。可是对于锁竞争比较激烈的场合,偏向锁就失效了,由于这样场合极有可能每次申请锁的线程都是不相同的,所以这种场合下不该该使用偏向锁,不然会得不偿失,须要注意的是,偏向锁失败后,并不会当即膨胀为重量级锁,而是先升级为轻量级锁。下面咱们接着了解轻量级锁。
(2)轻量级锁 、假若偏向锁失败,虚拟机并不会当即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6以后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁可以提高程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。须要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,若是存在同一时间访问同一锁的场合,就会致使轻量级锁膨胀为重量级锁。
(3)自旋锁轻量级锁失败后,虚拟机为了不线程真实地在操做系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数状况下,线程持有锁的时间都不会太长,若是直接挂起操做系统层面的线程可能会得不偿失,毕竟操做系统实现线程之间的切换时须要从用户态转换到核心态,这个状态之间的转换须要相对比较长的时间,时间成本相对较高,所以自旋锁会假设在不久未来,当前的线程能够得到锁,所以虚拟机会让当前想要获取锁的线程作几个空循环(这也是称为自旋的缘由),通常不会过久,多是50个循环或100循环,在通过若干次循环后,若是获得锁,就顺利进入临界区。若是还不能得到锁,那就会将线程在操做系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是能够提高效率的。最后没办法也就只能升级为重量级锁了。

四、锁的膨胀升级过程

 

 

 

注意一下几点:操作系统

线程1获取轻量级锁后会将Object Mark Word 复制本身的一份到本身的栈空间,而后在本身的栈空间开辟一个指针lockerecord 指向Object Mark Word,同时Object Mark Word也会指向lockerecord,当线程1执行完代码块释放轻量级锁以后,发现Object Mark Word不在指向本身,说明当前锁已经改成重量级锁,那么它会唤醒阻塞队列中全部线程从新竞争锁。线程

总结:偏向锁,轻量级锁都是基于Object Mark Word的标记实现,java尽量避免使用重量级锁。

相关文章
相关标签/搜索