本篇内容介绍了“LockSupport的原理和作用是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
用于创建锁和其他同步类的基本线程阻塞原语。
使用它,可以用来构建一些特定逻辑的锁。jdk并发包的AQS框架就依赖了它。
它提供了线程的阻塞和唤醒。
每当一个线程使用它,那么就可以当作这个线程维持了一个二元信号量,可以比作持有许可证。
调用pack()
方法,如果有许可证,那么消耗它并且直接返回,线程继续执行下去。反之,则阻塞。
调用unpack()
方法,如果许可证不可用,那么就变成可用,如果是可用,那么就不做其他操作。也就是说,许可证不可以多次累加。
因为有许可证这种中间介质,也就避免了调用线程不推荐的Thread.suspend()
和Thread.resume()
而导致的线程失活的竟态情况。这里我个人的理解是,线程调度的复杂性和延迟。比如说:线程的休眠和暂停并不能够做到即可马上、命令之间的重排等等。并且,park()
也额外支持了线程打断和超时功能。
类的文档里面额外提及了一个惯用法,说的是pack()
调用会"无理由"返回,从而导致线程莫名的执行下去。那么惯用法就是把pack()
逻辑包装在一个循环当中,并且循环退出的条件就是线程等待同步许可的条件。这就相当于一个自旋的优化,只是需要unpack()
配合。
一个阻塞线程的简单范例:
public static void main(String[] args) { LockSupport.park(); System.out.println("主线程阻塞,并不会打印当前消息"); }
一个通用的使用范式:
public static void main(String[] args) throws InterruptedException { Thread busThreadJim = new Thread(() -> { System.out.println("参数检查..."); System.out.println("执行业务逻辑..."); System.out.println("进入状态同步点"); LockSupport.park("因为可能原因A而阻塞住线程"); System.out.println("同步点完成,进步业务收尾阶段..."); System.out.println("进入最后一个同步点结束"); LockSupport.park("报告这个线程当前的工作程度或者状态xxxx"); System.out.println("业务逻辑完成退出"); }); busThreadJim.start(); System.out.println("控制线程或者监控线程开始做其他逻辑..."); Thread.sleep(2000); System.out.println("检查工作线程是否阻塞住,为什么:" + LockSupport.getBlocker(busThreadJim)); LockSupport.unpark(busThreadJim); // 如果线程调度慢 LockSupport.getBlocker获取到null // 也就是说工作线程还没有阻塞住 System.out.println("可以通过阻塞对象来传出一些线程工作信息:" + LockSupport.getBlocker(busThreadJim)); System.out.println("当然共享变量也是可以的"); LockSupport.unpark(busThreadJim); System.out.println("逻辑完成"); }
多次unpack()
也只能消耗一次
public static void main(String[] args) throws InterruptedException { Thread worker = new Thread(() -> { System.out.println("执行业务逻辑..."); System.out.println("进入状态同步点..."); LockSupport.park(); System.out.println("同步点完成,进步业务收尾阶段..."); LockSupport.park(); System.out.println("最后一个同步点结束"); }); worker.start(); LockSupport.unpark(worker); LockSupport.unpark(worker); Thread.sleep(2000); LockSupport.unpark(worker); }
设计代码逻辑的时候,尽量有明确的阻塞条件,然后选择带有时间截至的函数和提供阻塞信息的对象。
理论上来说,应该用不到这个类。但是一旦用到了,估计就是设计很基础的东西,上面肯定有复杂的逻辑。没有阻塞信息到时候逻辑排查是要命的。
wait/notify必须在同步代码块中使用,光这一点就感觉到限制比较大。
wait/notify操作的是对象,然后才能映射到对象所在的线程,思考方式有点饶。
复杂的逻辑,特别是嵌套,特别窒息。
public class LockSupport { private LockSupport() {} // 该类无法实例化 // 所有的功能都代理给内部来实现 private static final sun.misc.Unsafe UNSAFE; // 把当前线程阻塞住 public static void park() { UNSAFE.park(false, 0L); } // 把当前执行线程与一个阻塞对象相关连 // 这样子关注对象就变成线程而非对象 public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); } // 下面就是一系列时间相关函数 public static void parkNanos(Object blocker, long nanos) { if (nanos > 0) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, nanos); setBlocker(t, null); } } public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(true, deadline); setBlocker(t, null); } public static void parkNanos(long nanos) { if (nanos > 0) UNSAFE.park(false, nanos); } public static void parkUntil(long deadline) { UNSAFE.park(true, deadline); } // 唤醒目标也是线程 跟阻塞关注点相同 public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); } // 返回阻塞对象信息 // 如果频繁的pack,那么该对象可能一直在变更,它的生命周期会比较短,只是最近一次阻塞的信息 public static Object getBlocker(Thread t) { if (t == null) throw new NullPointerException(); return UNSAFE.getObjectVolatile(t, parkBlockerOffset); } }
值得注意的关注点在于blocker的信息获取,直接操作内存,因为线程已经阻塞住,只能通过这种机制来获取阻塞线程内信息。
“LockSupport的原理和作用是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。