提示: 关于 interrupt 你应该了解这些.
若不了解 interrupt 可以点击查看了解.
LockSupport的功能是使线程进行"驻留", 也就是让线程暂停工作. 线程暂停的状态有如下三种:
但是调用LockSupport的方法, 只可能进入WAITING
和TIMED_WAITING
状态, 不会进入BLOCKED
状态(原因可以查看Thread.State
). 进入的状态取决于是否设置超时时间.
当线程调用park
方法之后, 希望线程"取消驻留", 也就是让线程重新开始工作. 让线程重新开始工作的方法如下:
unpark
方法LockSupport是一个不能被实例化的类, 因为它只有一个构造方法,并且该构造方法是private. 构造方法如下图所示.
下面说一下常用的API, 以及这些API能有什么作用, 下图中红框中的是常用驻留的API, 蓝框是恢复的API.
park
的API作用是使调用的线程停止工作,进入等待状态.
API | 解释 |
---|---|
park() | 永久驻留, 等待其他线程恢复 |
park(Object) | 永久驻留, 等待其他线程恢复 (先忽视Object) |
parkNanos(long) | 驻留long纳秒, 若超时则自我恢复,也可在驻留阶段其他线程恢复 |
parkNanos(Object,long) | 同上 |
parkUntil(long) | 驻留到指定时间戳(毫秒), 若超时则自我恢复,也可在驻留阶段其他线程恢复 |
parkUntil(Object,long) | 同上 |
其中有的API有个Object参数, 该参数有什么作用?
该对象功能是标识该线程是因为这个对象到进入驻留, 只是起到一个标识,方便分析问题. 例如进行jstack分析线程状态.
unpark 的API作用是使线程恢复工作. 该API只有一个.unpark(Thread)
. 这里传入的Thread, 表示要恢复的Thread. 因此使用者(使用unpark(Thread)
)要能获取到被恢复线程的Thread对象.
先来一个简单的示例进行体会一下它的强大功能.
// 假设该方法是在任意一个类中. 然后运行该方法
public static void main(String[] args) {
Thread thread = Thread.currentThread();
new Thread(() -> {
try {
// 睡眠10秒
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用unpark,使 thread 线程唤醒
LockSupport.unpark(thread);
}).start();
System.out.println("Start to sleep...");
// park, 使调用线程进入等待
LockSupport.park();
System.out.println("Wake up.");
}
上面简单例子会立马输出Start to sleep
, 然后过了大约10
秒钟, 会接着输出Wake up.
.
注意: 线程会接着执行,位置是从调用park下一行代码
再来看一个LockSupport给出的示例.
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();
}
// unlock方法
public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}
上面的例子是做了一个有序的互斥量, 谁先调用谁就有优势.
// 新建锁
FIFOMutex fifoMutex = new FIFOMutex();
// A线程
fifoMutex.lock();
try {
// 做一些事情, 需要比较久.
} finally {
fifoMutex.unlock();
}
// B线程, 如果A线程已经调用 lock, 则B线程会被阻塞.
fifiMutex.lock();
try {
// 做一些事情.
} finally {
fifoMutex.unlock();
}
首先获取现在的线程对象(currentThread
), 并且将对象加入到队列中(waiters.add(current)
). 然后是while循环, 判断队列头是否是自己的线程, 如果是则CAS设置锁, 如果设置不成功, 则说明有其他线程正在使用, 调用LockSupport.park(this)阻塞自己. 等待其他线程唤醒自己.
将锁的标志变为false, 代表没有线程正在使用. 然后唤醒下一个线程.
思考: 假如 B 线程先 unlock -> lock -> unlock 会有什么问题?