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()
的使用不会出现死锁的情况。