阻塞锁
大约 3 分钟
阻塞锁
1、阻塞锁优势
与自旋锁不同,改变了线程的运行状态。
在JAVA环境中,线程Thread有如下几个状态:
新建状态
就绪状态
运行状态
阻塞状态
死亡状态
阻塞锁,可以说是让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。
JAVA中,能够进入 / 退出、阻塞状态或包含阻塞锁的方法有 ,synchronized 关键字(其中的重量锁),ReentrantLock,Object.wait() / notify() ,LockSupport.park() / unpart()
2、阻塞锁的优势:
在于,阻塞的线程不会占用CPU时间, 不会导致 CPU占用率过高,但进入时间以及恢复时间都要比自旋锁略慢。在竞争激烈的情况下 阻塞锁的性能要明显高于自旋锁。
3、阻塞锁应用:
理想的情况则是, 在线程竞争不激烈的情况下,使用自旋锁;竞争激烈的情况下使用,阻塞锁。
4、阻塞锁的简单实现:
public class ClhLock {
/**
* 定义一个节点,默认的lock状态为true
*/
public static class ClhNode {
private volatile Thread isLocked;
}
/**
* 尾部节点,只用一个节点即可
*/
private volatile ClhNode tail;
private static final ThreadLocal<ClhNode> LOCAL = new ThreadLocal<>();
private static final AtomicReferenceFieldUpdater<ClhLock, ClhNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(ClhLock.class, ClhNode.class, "tail");
public void lock() {
// 新建节点并将节点与当前线程保存起来
ClhNode node = new ClhNode();
LOCAL.set(node);
// 将新建的节点设置为尾部节点,并返回旧的节点(原子操作),这里旧的节点实际上就是当前节点的前驱节点
// 个人理解=>大概相当于把AtomicReferenceFieldUpdater中原有的tail取出,并用新建的节点将原有的tail替代,这个操作是原子性的。
// 操作原子性的由来:AtomicReferenceFieldUpdater是一个基于反射的工具类,它能对指定类的指定的volatile引用字段进行原子更新。(这个字段不能是private的)。
ClhNode preNode = UPDATER.getAndSet(this, node);
if (preNode != null) {
preNode.isLocked = Thread.currentThread();
LockSupport.park(this);
preNode = null;
LOCAL.set(node);
}
// 如果不存在前驱节点,表示该锁没有被其他线程占用,则当前线程获得锁
}
public void unLock() {
// 获取当前线程对应的节点
// 对应博客中的这句话:申请线程只在本地变量上自旋,避免了多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum
// 每次读写操作都必须在多个处理器缓存之间进行缓存同步
ClhNode node = LOCAL.get();
// 如果tail节点等于node,则将tail节点更新为null,同时将node的lock状态职位false,表示当前线程释放了锁
if (!UPDATER.compareAndSet(this, node, null)) {
// System.out.println("unlock\t" + node.isLocked.getName());
LockSupport.unpark(node.isLocked);
}
node = null;
}
}
5、demo:
public class ClhLockTest {
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1000, 1000, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new DefaultNameThreadFactory("SimpleSpinLock"));
CountDownLatch countDownLatch = new CountDownLatch(1000);
ClhLock clhLock = new ClhLock();
for (int i = 0; i < 1000; i++) {
pool.submit(() -> {
clhLock.lock();
num++;
clhLock.unLock();
// 计数减一
countDownLatch.countDown();
});
}
// 要求主线程等待所有任务全部准备好才一起并行执行
countDownLatch.await();
System.out.println(num);
}
}