跳至主要內容

阻塞锁

zheng大约 3 分钟java基础

阻塞锁

1、阻塞锁优势

与自旋锁不同,改变了线程的运行状态。

在JAVA环境中,线程Thread有如下几个状态:
  1. 新建状态

  2. 就绪状态

  3. 运行状态

  4. 阻塞状态

  5. 死亡状态

    阻塞锁,可以说是让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。
    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);
     }
 }

 
上次编辑于:
贡献者: 郑天祺