1 引言
上一篇简单介绍了Java中的Thread机制,附传送门↓:
Java并发编程(一):了解Thread
我们知道,可以通过wait()让线程等待,通过notify()唤醒线程,但使用wait(),notify()来实现等待唤醒功能至少有两个缺点:
-
wait()和notify()都是Object中的方法,在调用这两个方法前必须先获得锁对象,这限制了其使用场合:只能在同步代码块中。 -
另一个缺点是当对象的等待队列中有多个线程时,
notify()只能随机选择一个线程唤醒,无法唤醒指定的线程。
而使用LockSupport的话,我们可以在任何场合使线程阻塞,同时也可以指定要唤醒的线程,较为方便。
2 LockSupport的应用
首先列举LockSupport常用的几个方法:
// 暂停当前线程
public static void park(Object blocker);
// 暂停当前线程,不过有超时时间的限制
public static void parkNanos(Object blocker, long nanos);
// 暂停当前线程,直到某个时间
public static void parkUntil(Object blocker, long deadline);
// 无期限暂停当前线程
public static void park();
// 暂停当前线程,不过有超时时间的限制
public static void parkNanos(long nanos);
// 暂停当前线程,直到某个时间
public static void parkUntil(long deadline);
// 恢复指定线程的运行
public static void unpark(Thread thread);
public static Object getBlocker(Thread t);
方法入参的
Object blocker是代表什么呢?这其实就是方便在线程dump的时候看到具体的阻塞对象的信息。
再通过一个小栗子简单演示LockSupport的用法:
/**
* @author Carson Chu, 1965704869@qq.com
* @date 2020/4/4 10:41
* @description
*/
public class Main {
public static void main(String[] args) {
FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return true;
});
Thread thread1 = new Thread(futureTask, "thread-1");
Thread thread2 = new Thread(() -> {
for (int i = 2; i < 4; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
/* 让线程等待 */
LockSupport.park();
}
}, "thread-2");
thread1.start();
thread2.start();
LockSupport.unpart(thread2);
}
}
由上述代码可以看出,线程2在调度的时候,进入循环第一次输出数字时,调用的LockSupport.park()方法,所以线程2只会输出一个数字然后进入等待状态。当然最后不要忘记调用LockSupport.unpark()唤醒。
在了解LockSupport的等待唤醒的基本操作之后,再来看下面一段代码:
public class Main {
public static void main(String[] args) {
FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
Thread currentThread = Thread.currentThread();
for (int i = 0; i < 2; i++) {
System.out.println(currentThread.getName() + ":" + i);
/* 唤醒线程 */
LockSupport.unpark(currentThread);
}
/* 让线程等待 */
LockSupport.park();
return true;
});
Thread thread1 = new Thread(futureTask, "thread-1");
thread1.start();
}
}
上述代码关键在于唤醒操作执行在了等待操作之前,那么线程是否会发生死锁呢?
答案是不会,这点不同于stop和resume机制,stop和resume如果顺序反了,会出现死锁现象。
那么是什么原因呢?
让我们来结合源码分析。
首先贴出park()的实现原理:
public static void park() {
/* 该方法是本地方法native */
UNSAFE.park(false, 0L);
}
再贴出unpark()的实现原理:
public static void unpark(Thread thread) {
if (thread != null)
/* 该方法是本地方法native */
UNSAFE.unpark(thread);
}
观察上述源码,尤其是park(),眼光锐利如你,相信一定看到了false这个关键字,没错,这就是重点。实际上LockSupport底层维护了一个许可(permit),即上述源码里的布尔值。那么它是什么原理呢?一言以蔽之就是:
park()调用时,判断许可是否为true,如果是true,则继续往下执行;如果是false,则调用线程等待,直到许可为true。当unpark()调用时,如果当前线程还未进入park(),则许可为true。
再结合上述样例代码,我先调用unpark()方法,此时线程还有进入park(),所以许可为true,之后再调用park()方法时,判断许可值为true,则线程不会执行等待操作,而是继续执行,因而不会发生死锁。
小结
park()和unpark()可以实现类似wait()和notify()的功能,但是并不和wait()和notify()交叉,也就是说unpark()不会对wait()起作用,notify()也不会对park()起作用。park()和unpark()的使用不会出现死锁的情况。
本文深入探讨Java并发编程中LockSupport的使用,包括park()和unpark()方法的原理及如何避免死锁,对比wait()和notify(),并提供代码示例。
591

被折叠的 条评论
为什么被折叠?



