个人简介:浪荡不羁,一生所爱。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()的阻塞锁。
理想的情况则是:在线程竞争不激烈的情况下,使用自旋锁,竞争激烈的情况下使用,阻塞锁。