性能调优之锁优化

线程安全问题触发条件:

  1.存在共享数据

  2.多个线程同时操作共享数据

锁之syncronized

  特征:可重入(获得了外面的锁,则自动获得了里面的锁),非公平(线程有可能会饿死)

  原理:

    1)早期:

      

 

     2)堆内存中对象存储方式(锁,本质上是堆中某个对象的对象头中通过mark word标记的一种状态):

      1.对象头

        1)Mark Word,用来存储对象自身的运行时数据,例如对象哈希code,锁状态的标志,线程所持有的锁等

        2)类型指针,虚拟机通过该指针来确定这个对象是哪一个类的实例

        3)数组长度(仅当对象是数组的时候)

      2.实例数据

      3.对齐填充

    3)锁状态,其中重量级锁就是syncronized早期锁,又叫常规锁。从上到下,性能 依次降低,开销依次递增。

      

 

       偏向锁:

        1.默认启动5s后开启,当只有一个线程竞争锁资源的时候,相比于关闭偏向锁的应用,性能有小幅度提升(2%),当线程竞争激烈的时候,使用偏向锁,性能反而会有所下降。

        2.关闭方式,添加运行时参数:-XX:-UseBiasedLocking

        3.开启并取消延迟:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

      轻量级锁:即乐观锁,通过CAS操作竞争锁资源

      重量级锁:即悲观锁,线程之间完全互斥。

    4)锁升级过程

      syncronized锁有一个升级过程:

        1.检测是无锁还是偏向锁

        2.如果是偏向锁,校验线程id,看是不是同一个线程在竞争锁资源,如果是,直接执行同步代码,如果不是,CAS替换线程id。

        3.如果替换成功,执行同步代码,如果替换失败,说明已经有别的线程Y占用了锁,则撤销偏向锁,暂停线程Y并检查状态,如果不活动或已经退出,则释放线程Y,唤醒线程X,同时X获得锁

        4.如果线程Y活动,则升级为轻量级锁,由线程Y持有

        5.线程X进行CAS操作,如果成功,则获得锁并执行同步代码,如果失败,进行自旋操作 ,在限制次数内,如果成功,就获得该轻量级锁。

        6.如果超过自旋次数,线程X仍未获得锁,则说明锁资源竞争相当激烈,此时锁就会升级为重量级锁,通过操作系统的互斥锁来实现。

  使用场景:

      

  优化机制:

    1.逃逸分析(又叫做锁消除):分析变量能否逃出他的作用域,如果不能逃出,jvm会自动取消掉他的锁

      下图中,object变量是一个局部变量,且不可能发生逃逸,所以不是共享变量,不可能发生线程安全问题,jvm会自动取消掉这段syncronized代码块。

      

 

        而如果代码写成这样,则可能发生逃逸,因为object作为someMethod的返回值,且赋值给了object2,而object2又是一个成员变量,所以可能发生线程安全问题。  

        

 

       2.锁粗化:将多个连续的加锁解锁操作连接在一起,扩展成一个范围更大的锁。

          

          上图代码会被优化成下面代码

          

 

       逃逸分析和锁粗化默认开启,如要手动开启,添加如下运行时参数 

       

 

      3.锁分级:也就是上文所说的偏向锁,轻量级锁,重量级锁

    总结:syncronized优化机制包括锁分级,锁消除,锁粗化。

        

 

 

锁之reentrantLock

  核心API:

    1.lock:表示获得锁

    2.tryLock:表示尝试获得加锁,如果失败返回false

    3.tryLock(timeout,TimeUnit):带超时的tryLock

    4.lockInterruptibly:如果当前线程interrupt了就抛异常,如果没有就去获得锁

       5.unlock:解锁

  示例代码:

    

  特性:

    1.互斥

    2.可重入   

    3.公平性:

      1)线程会按照向lock申请锁的顺序去依次获得锁,不允许插队,牺牲了一部分性能来获得公平性,这一点是syncronized锁不具备的

      2)创建锁对象的时候传入一个boolean参数,true表示创建一个公平锁,false表示创建一个非公平锁,不传则默认创建的是非公平锁  

      

 

       

 

     4.condition:替代传统线程通信中,object的wait(),notify()方法

      核心API:

        

 

       注意点:操作condition的代码,必须在lock的保护之下

  原理:基于AQS和CAS  

 

面试题:syncronized和reentrantLock的区别?

  1.syncronized基于jvm实现,看不到源码,而reentrantlock基于jdk实现,方便查看源码

  2.jdk1.6之前,syncronized性能比reentrantlock差很多,1.6之后,性能差不多

  3.reentrantlock实现了公平性,有丰富的api,并且有condition操作,syncronized不具备

  4.使用时,如果两者都可以满足需要,官方建议使用syncronized,代码更加简洁易读。当然,也可以根据自己的喜好来。

      

 锁之ReentrantReadWriteLock(读写锁)

  适用于读多写少的场景,实际上是两个锁,一个readlock,一个writelock

  

 

     

 

   

 

   示例代码:

    

 

   特性:

    1.公平性

    2.可重入

    3.锁降级:一个写锁可以降级成读锁,但是一个读锁不能升级成一个写锁

 

面试题:reentrantlock和reentrantReadWriteLock的区别?

  1.reentrantlock完全互斥

  2.reentrantReadWriteLock读锁共享,写锁互斥,在读多写少的场景下,reentrantReadWriteLock的性能要比reentrantlock好很多,但是在写多场景下,优势就不明显了。

 

 

锁之StampedLock

   

 

   背景:

    reentrantReadWriteLock存在的问题:

      1.写线程“饥饿”问题,由于一个线程尝试获取写锁的时候,不能有其他的读锁存在,所以如果读线程非常多,该线程可能久久拿不到写锁,存在饥饿问题

      2.写锁可以降级成读锁,但是读锁却不能升级成写锁。

  特性:

   

 

 

     

 

     

 

       

 

 

 锁调优:

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

      

posted @ 2020-10-19 19:34  红嘴鲤鱼  阅读(265)  评论(0编辑  收藏  举报