zoukankan      html  css  js  c++  java
  • JUC 包下工具类,它的名字叫 LockSupport !你造么?

    前言

    LockSupport 是 JUC 中常用的一个工具类,主要作用是挂起和唤醒线程。在阅读 JUC 源码中经常看到,所以很有必要了解一下。

    公众号:liuzhihangs ,记录工作学习中的技术、开发及源码笔记;时不时分享一些生活中的见闻感悟。欢迎大佬来指导!

    介绍

    基本线程阻塞原语创建锁和其他同步类。Basic thread blocking primitives for creating locks and other synchronization classes.

    LockSupport 类每个使用它的线程关联一个许可(在意义上的Semaphore类)。 如果许可可用,调用 park 将立即返回,并在此过程中消费它; 否则可能阻塞。如果许可不是可用,可以调用 unpark 使得许可可用。(但与Semaphore不同,许可不能累积。最多有一个。)

    方法 park 和 unpark 提供了阻塞的有效手段和解锁线程不会遇到死锁问题,而 Thread.suspend 和 Thread.resume 是不能用于这种目的:因为许可的存在,一个线程调用 park 和另一个线程试图 unpark 它之间的竞争将保持活性。 此外,如果调用者线程被中断,park 将返回,并且支持设置超时。 该 park 方法也可能返回在其他任何时间,“毫无理由”,因此通常必须在一个循环中调用的返回后重新检查条件。 在这个意义上park作为“忙碌等待”不会浪费太多的时间自旋的优化,但必须以配对 unpark 使用。

    这三种形式的 park 还支持 blocker 对象参数。而线程被阻塞时是允许使用监测和诊断工具,以确定线程被阻塞的原因。(诊断工具可以使用getBlocker(Thread) 方法 。)同时推荐使用带有 blocker 参数的 park方法,通常做法是 blocker 被设置为 this 。

    上面的意思总结下来个人理解是:

    1. 许可(permit)的上限是1,也就是说只有 0 或 1 。
    2. park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,然后返回。
    3. unpark:没有许可的时候,permit 为 0 ,调用 unpark 会增加一个许可,因为许可上限是 1 , 所以调用多次也只会为 1 个。
    4. 线程初始的时候是没有许可的。
    5. park 的当前线程如果被中断,会立即返回,并不会抛出中断异常。
    6. park 方法的调用一般要放在一个循环判断体里面。

    大概如图所示:

    rGm1AX

    下面是源码注释中的案例:

    /**
     * FIFO 独占锁
     */
    class FIFOMutex {
       private final AtomicBoolean locked = new AtomicBoolean(false);
       private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
    
       public void lock() {
         boolean wasInterrupted = false;
         Thread current = Thread.currentThread();
         waiters.add(current);
    
         // Block while not first in queue or cannot acquire lock
         // 不在队列头,或者锁被占用,则阻塞, 就是只有队列头的可以获得锁
         while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
           LockSupport.park(this);
           if (Thread.interrupted()) // ignore interrupts while waiting
             wasInterrupted = true;
         }
    
         waiters.remove();
         if (wasInterrupted)          // reassert interrupt status on exit
           current.interrupt();
       }
    
       public void unlock() {
         locked.set(false);
         LockSupport.unpark(waiters.peek());
       }
     }
    
    

    验证

    线程初始有没有许可?

    public class LockSupportTest {
    
        public static void main(String[] args) {
    
            System.out.println("开始执行……");
    
            LockSupport.park();
    
            System.out.println("LockSupport park 之后……");
    
        }
    }
    
    1. 执行后会发现,代码在 park 处阻塞。说明,线程初始是没有许可的。

    添加许可并消耗许可

    public class LockSupportTest {
    
        public static void main(String[] args) {
    
            System.out.println("开始执行……");
    
            LockSupport.unpark(Thread.currentThread());
    
            System.out.println("执行 - park");
            
            LockSupport.park();
    
            System.out.println("LockSupport park 之后……");
    
        }
    
    }
    
    public class LockSupportTest {
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park");
                    LockSupport.park(this);
                    System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 结束");
                }
            });
    
            thread.start();
            // 保证 上面线程先执行,然后再主线程
            Thread.sleep(5000);
            System.out.println("开始执行 unpark(thread)");
            LockSupport.unpark(thread);
            Thread.sleep(5000);
            System.out.println("执行 unpark(thread) 结束");
    
        }
    
    }
    
    

    通过上面示例可以看出:

    1. 执行 unpark 可以进行给予指定线程一个证书。
    2. 线程当前被 park 阻塞,此时给予证书之后, park 会消耗证书,然后继续执行。

    许可上限为 1

    
    public class LockSupportTest {
    
        public static void main(String[] args) {
    
            System.out.println("unpark 1次");
            LockSupport.unpark(Thread.currentThread());
            System.out.println("unpark 2次");
            LockSupport.unpark(Thread.currentThread());
    
            System.out.println("执行 - park 1 次");
            LockSupport.park();
            System.out.println("执行 - park 2 次");
            LockSupport.park();
            
            System.out.println("LockSupport park 之后……");
    
        }
    
    }
    
    1. 线程阻塞,可以看出 permit 只能有一个

    中断可以使 park 继续执行并不会抛出异常

    public class LockSupportTest {
        public static void main(String[] args)  {
    
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park");
                    LockSupport.park(this);
                    System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 结束");
    
                    System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park 第二次");
                    LockSupport.park(this);
                    System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 第二次 结束");
    
                }
            });
    
            try {
                thread.start();
                // 保证 上面线程先执行,然后再主线程
                Thread.sleep(5000);
                System.out.println("开始执行 unpark(thread)");
                // LockSupport.unpark(thread);
                thread.interrupt();
                Thread.sleep(5000);
                System.out.println("执行 unpark(thread) 结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    

    输出结果:

    /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java ...
    线程 Thread-0开始执行 park
    开始执行 unpark(thread)
    线程 Thread-0执行 park 结束
    线程 Thread-0开始执行 park 第二次
    线程 Thread-0执行 park 第二次 结束
    执行 unpark(thread) 结束
    
    1. 可以看出线程中断,park 会继续执行,并且没有抛出异常。
    2. thread.interrupt(); 调用之后, 设置线程中断标示,unpark 没有清除中断标示,第二个 park 也会继续执行。

    使用诊断工具

    liuzhihang % > jps
    76690 LockSupportTest
    77130 Jps
    liuzhihang % > jstack 77265
    ...
    "main" #1 prio=5 os_prio=31 tid=0x00007f7f3e80a000 nid=0xe03 waiting on condition [0x000070000dfcd000]
       java.lang.Thread.State: WAITING (parking)
            at sun.misc.Unsafe.park(Native Method)
            at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
            at com.liuzhihang.source.LockSupportTest.main(LockSupportTest.java:14)
    
    1. 中间省略部分,但是可以看出线程进入 WAITING 状态

    源码分析

    
    public class LockSupport {
    
        private static final sun.misc.Unsafe UNSAFE;
    
        /**
         * 为线程 thread 设置一个许可
         * 无许可,则添加一个许可,有许可,则不添加
         * 如果线程因为 park 被阻塞, 添加许可之后,会解除阻塞状态
         */
        public static void unpark(Thread thread) {
            if (thread != null)
                UNSAFE.unpark(thread);
        }
    
        /**
         * 有许可,则使用该许可
         * 没有许可,阻塞线程,直到获得许可
         * 传递 blocker 是为了方便使用诊断工具
         */
        public static void park(Object blocker) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, 0L);
            setBlocker(t, null);
        }
    
        /**
         * 设置线程的 blocker 属性
         */
        private static void setBlocker(Thread t, Object arg) {
            // Even though volatile, hotspot doesn't need a write barrier here.
            UNSAFE.putObject(t, parkBlockerOffset, arg);
        }
    }
    

    LockSupport 的 park unpark 方法,实际调用的是底层 Unsafe 类的 native 方法。

    
    public final class Unsafe {
        
        public native void unpark(Object var1);
    
        public native void park(boolean var1, long var2);
    }
    

    既然调用了 Unsafe 到此处肯定不能善罢甘休。

    ffecad2c8644bab7d152be118c31722d

    hotspot 源码

    这块是下载的官方包中的源码,阅读并查阅资料了解的大概逻辑,不清楚之处,希望指导出来。

    也可以直接跳过直接看结论。

    查看jdk源码
    http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5a83b7215107/src/share/vm/runtime/park.hpp

    这块在以 os_linux 代码为例
    http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5a83b7215107/src/os/linux/vm/os_linux.cpp

    c-parker
    c-park
    c-unpark

    1. 在底层维护了一个 _counter 通过更新 _counter 的值来标示是否有证明。
    2. 在 park 时,判断 _counter 为 0,则阻塞等待,为 1 则获得更新为 0 并返回。
    3. 在 unpark 时,判断 _counter 为 0,则给予凭证,并唤醒线程,为 1 则直接返回。

    总结

    总结也是和预想的是相同的。

    1. 许可(permit)的上限是1,也就是说只有 0 或 1 。
    2. park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,然后返回。
    3. unpark:没有许可的时候,permit 为 0 ,调用 unpark 会增加一个许可,因为许可上限是 1 , 所以调用多次也只会为 1 个。
    4. 线程初始的时候是没有许可的。
    5. park 的当前线程如果被中断,会立即返回,并不会抛出中断异常。

    扩展

    • park/unpark 和 wait/notify 区别
    1. park 阻塞当前线程,unpark 唤醒指定线程。
    2. wait() 需要结合锁使用,并释放锁资源,如果没有设置超时时间,则需要 notify() 唤醒。而 notify() 是随机唤醒线程。
  • 相关阅读:
    工作总结
    JSON数据使用
    DataTable知识
    树形结构菜单
    区域树前后台
    跨域总结
    工作一年感想
    项目整体架构分析
    springboot 和 mongdb连接问题 Exception in thread "main" com.mongodb.MongoSecurityException:
    go函数、方法、接口笔记
  • 原文地址:https://www.cnblogs.com/liuzhihang/p/locksupport.html
Copyright © 2011-2022 走看看