关于park()阻塞线程的唤醒方式
Park阻塞线程唤醒有两种方式:
1、中断
2、unlock->release()->unpark()
中断唤醒:
interrupt()存在的意义
早期停止线程的方式是Thread里有个native的方法stop0(),相当于linux里的kill -9,会产生各种问题,因此,Java提供了一种用于停止线程的机制——中断。
上代码演示效果
public static void main(String[] args) {
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
Thread current = Thread.currentThread();
log.info("{},开始执行!", current.getName());
for (; ; ) {//spin 自旋
log.info("准备park住当前线程:{}....", current.getName());
LockSupport.park();
log.info("当前线程{}已经被唤醒....", current.getName());
}
}
}, "t0");
t0.start();
try {
Thread.sleep(2000);
log.info("准备唤醒{}线程!", t0.getName());
LockSupport.unpark(t0);
Thread.sleep(10000);
t0.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
执行结果:
10:42:04.711 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - t0,开始执行!
10:42:04.715 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:42:06.722 [main] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备唤醒t0线程!
10:42:06.723 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:42:06.723 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:43:07.722 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
.........
.........
.........
后面无限循环,park()执行了之后,没有效果,线程不会阻塞,所以一直重复执行两个log语句。
参照AQS里ReentraintLock源码,
public class TestReentrantLock {
/**
* 可重入锁,怎么实现类似于synchronized的功能
*/
public static ReentrantLock lock = new ReentrantLock(true);
static boolean flag = false;
public static void main(String[] args) {
List<Thread> list = new ArrayList<>();
for (int i=0;i<10;i++){
Thread t = new Thread(()->{
lock.lock();
System.out.println(Thread.currentThread().getName()+"get lock");
//拿到中断信号
while (!flag){
if(flag){
break;
}
}
lock.unlock();
},"t-"+i);
list.add(t);
t.start();
}
try {
Thread.sleep(2000);
list.get(3).interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
t-1get lock
两秒后t-3,发出中断信号,解除阻塞
下一轮循环,继续去抢锁,因为获取锁的t-1一直没有释放,故仍旧抢不到,t-3需要继续park()住,也就是阻塞在parkAndCheckInterrupt()里。
按照之前的例子,直接t-3.interrupt(),下次park()是阻塞不住的,重点在于唤醒了之后需要清除中断状态,即Thread.interrupted(),然后才能确保下次park()时能阻塞住。
改造开始的例子,验证
public static void main(String[] args) {
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
Thread current = Thread.currentThread();
log.info("{},开始执行!", current.getName());
for (; ; ) {//spin 自旋
log.info("准备park住当前线程:{}....", current.getName());
LockSupport.park();
if (Thread.currentThread().isInterrupted()) {
Thread.interrupted();
System.out.println("清除中断标记后,线程可以继续park住,如果中断标记为true,即使调用park(),线程也不会阻塞");
}
log.info("当前线程{}已经被唤醒....", current.getName());
}
}
}, "t0");
t0.start();
try {
Thread.sleep(2000);
log.info("准备唤醒{}线程!", t0.getName());
LockSupport.unpark(t0);
Thread.sleep(10000);
t0.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出:
10:58:37.008 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - t0,开始执行!
10:58:37.011 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
10:58:39.020 [main] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备唤醒t0线程!
10:58:39.020 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:58:39.020 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....
清除中断标记后,线程可以继续park住,如果中断标记为true,即使调用park(),线程也不会阻塞
10:58:49.036 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 当前线程t0已经被唤醒....
10:58:49.036 [t0] INFO com.yg.edu.lock.Juc01_Thread_LockSupport - 准备park住当前线程:t0....