本篇内容介绍了“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的原理和作用是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/snakevash123/blog/3119065