Java锁之阻塞锁分析

2,049 阅读3分钟

个人简介:浪荡不羁,一生所爱。Java耕耘者(微信公众号ID:Java耕耘者),欢迎关注。可获得2000G详细的2020面试题的资料

今天所说的阻塞锁与自旋锁的不同,在于改变了线程的运行状态,让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。

注:线程Thread有如下几个状态:新建状态、就绪状态、运行状态、阻塞状态、死亡状态

优缺点分析:

阻塞的线程不会占用cpu时间,不会导致 CPU占用率过高,但进入时间以及恢复时间都要比自旋锁略慢。

在竞争激烈的情况下,阻塞锁的性能要明显高于自旋锁。

JAVA中,能够进入\退出、阻塞状态或包含阻塞锁的方法有 ,synchronized 关键字(其中的重量锁),ReentrantLock(重入锁ReentranLock详解、实战、及与对比Synchronized),Object.wait()\notify(),LockSupport.park()/unpart()(JUC(Java Util Concurrency)经常使用).

PS:既然提到重量锁,就说下其他的相关的几个,针对synchronized来说,锁有偏向锁、轻量级锁、重量级锁。

  • 偏向锁:对于一段同步代码来说,锁偏向于第一次获取它的线程,如果继续执行的过程中,锁没有被其它线程持有,则持有偏向锁的线程将不需要同步,自动获取锁。加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。如果线程间存在锁竞争,会带来额外的锁撤销的消耗。适用于只有一个线程访问同步块场景。

  • 轻量级锁:当偏向锁被另一个线程持有的时候,偏向锁升级为轻量级锁,其它线程通过自旋转的方式尝试获取锁。竞争的线程不会阻塞,提高了程序的响应速度。如果始终得不到锁竞争的线程使用自旋会消耗CPU。适用于追求响应时间、同步块执行速度非常快。

  • 重量级锁:当轻量级锁被另一个线程持有的时候,轻量级锁升级为重量级锁。线程竞争不使用自旋,不会消耗CPU。缺点就是线程阻塞,响应时间缓慢。适用于追求吞吐量,同步块执行速度较长。

代码实例:

CLHLock1.java(该例子是CLH锁修改而成)

public class CLHLock1 {   
 public static class CLHNode {       
 private volatile Thread isLocked;    
}   
 @SuppressWarnings("unused")    
private volatile CLHNode   tail;   
 private static final ThreadLocal<CLHNode>    LOCAL   = new ThreadLocal<CLHNode>();  
  private static final AtomicReferenceFieldUpdater<CLHLock1, CLHNode>
 UPDATER  = AtomicReferenceFieldUpdater.newUpdater(CLHLock1.class, CLHNode.class, "tail");  
     public void lock() {       
 CLHNode node = new CLHNode();        
LOCAL.set(node);       
 CLHNode preNode = UPDATER.getAndSet(this, node);       
 if (preNode != null) {            
preNode.isLocked = Thread.currentThread();          
  LockSupport.park(this);           
 preNode = null;            
LOCAL.set(node);      
  }   
 } 
   public void unlock() {      
  CLHNode node = LOCAL.get();       
 if (!UPDATER.compareAndSet(this, node, null)) {           
 System.out.println("unlock\t" + node.isLocked.getName());           
 LockSupport.unpark(node.isLocked);      
  }    
    node = null;   
 }
}

    在这里我们使用了LockSupport.unpark()的阻塞锁。

理想的情况则是:在线程竞争不激烈的情况下,使用自旋锁,竞争激烈的情况下使用,阻塞锁。