首先声明,本文不会去贴native方法的cpp实现,而是以伪代码的形式来理解这些native方法。
中断状态
,我们可以认为它是一个bool型的变量。初始为false。permit
,我们可以认为它是一个int型的变量,但它的值只能为0或1。当为1时,再累加也会维持1。初始为0。下面将以伪代码的实现来说明park/unpark的实现。
park() {
if(permit > 0) {
permit = 0;
return;
}
if(中断状态 == true) {
return;
}
阻塞当前线程; // 将来会从这里被唤醒
if(permit > 0) {
permit = 0;
}
}
可见,只要permit
为1或者中断状态
为true,那么执行park
就不能够阻塞线程。park
只可能消耗掉permit
,但不会去消耗掉中断状态
。
unpark(Thread thread) {
if(permit < 1) {
permit = 1;
if(thread处于阻塞状态)
唤醒线程thread;
}
}
unpark
一定会将permit
置为1,如果线程阻塞,再将其唤醒。从实现可见,无论调用几次unpark
,permit
只能为1。
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.park(); //因为此时permit为0且中断状态为false,所以阻塞
}
}
上面程序执行后,程序不会运行结束,main线程阻塞。
原因是,线程默认的permit
是0,中断状态
为false,所以会阻塞当前线程;
。
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.unpark(Thread.currentThread()); //置permit为1
LockSupport.park(); //消耗掉permit后,直接返回了
}
}
上面程序执行后,程序运行结束。
原因是LockSupport.unpark(Thread.currentThread())
执行后,会使得main线程的permit
为1。而park
时发现这个permit
为1时,就会消耗掉这个permit
,然后直接返回,所以main线程没有阻塞。
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.unpark(Thread.currentThread());
LockSupport.park(); //消耗掉permit后,直接返回了
LockSupport.park(); //此时permit为0,中断状态为false,必然会阻塞
}
}
上面程序执行后,程序不会运行结束,main线程阻塞。
原因是第二次park
时,permit
为0了,中断状态为false,所以会阻塞当前线程;
。
public class test3 {
public static void main(String[] args){
Thread main = Thread.currentThread();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程开始睡觉");
try {
Thread.sleep(1000);//睡一下保证是在main线程park后,才去unpark main线程
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"抛出了中断异常");
}
System.out.println("子线程睡醒了,开始unpark main线程");
LockSupport.unpark(main);
}
}).start();
LockSupport.park(); //此时permit为0,中断状态为false,必然会阻塞
//被子线程unpark后,从上一句被唤醒,继续执行。此时permit还是为0,中断状态为true。
LockSupport.park(); //此时permit为0,中断状态为false,必然会阻塞
}
}
上面程序执行后,程序不会运行结束,main线程阻塞。
这个程序同上,只是之前的版本都是先unpark
,再park
。现在保证是,main线程先park
后,再去unpark
main线程。
interrupt(){
if(中断状态 == false) {
中断状态 = true;
}
unpark(this); //注意这是Thread的成员方法,所以我们可以通过this获得Thread对象
}
interrupt()
会设置中断状态
为true。注意,interrupt()
还会去调用unpark
的,所以也会把permit
置为1的。
public class test3 {
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().interrupt();
LockSupport.park(); //消耗掉permit后,直接返回了
}
}
上面程序执行后,程序运行结束。因为park
执行时permit
为1,直接返回了。
public class test3 {
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().interrupt();
LockSupport.park(); //消耗掉permit后,直接返回了
LockSupport.park(); //因为中断状态 == true,直接返回了
LockSupport.park(); //同上
}
}
上面程序执行后,程序运行结束。马上无论怎么park
都无法阻塞线程了,因为此时线程的中断状态
为true,函数直接返回了。
public class test3 {
public static void main(String[] args) throws InterruptedException {
Thread main = Thread.currentThread();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("马上开始睡觉");
try {
Thread.sleep(1000);//睡一下保证是在main线程阻塞后,才去中断main线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡醒了,开始中断main线程");
main.interrupt();
}
}).start();
LockSupport.park(); //此时permit为0,中断状态为false,必然会阻塞
//被子线程中断后,从上一句被唤醒,继续执行。此时permit为0,中断状态为true。
LockSupport.park(); //因为中断状态 == true,直接返回了
LockSupport.park(); //同上
}
}
上面程序执行后,程序运行结束。
这个程序同上,只是之前的版本都是先中断,再park。现在保证是,main线程先阻塞后,再去中断main线程。
sleep(){//这里我忽略了参数,假设参数是大于0的即可
if(中断状态 == true) {
中断状态 = false;
throw new InterruptedException();
}
线程开始睡觉;
if(中断状态 == true) {
中断状态 = false;
throw new InterruptedException();
}
}
sleep()
会去检测中断状态,如果检测到了,那就消耗掉中断状态后,抛出中断异常。但sleep()
不会去动permit
。
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
try {
Thread.sleep(1000); // 消耗掉中断状态后,抛出异常
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面程序执行后,抛出异常,程序运行结束。
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
try {
Thread.sleep(1000); // 消耗掉中断状态后,抛出异常
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park(); //消耗掉permit
}
}
上面程序执行后,抛出异常,程序运行结束。
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
try {
Thread.sleep(1000);//消耗掉中断状态
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park(); //消耗掉permit
LockSupport.park(); //因为此时permit为0且中断状态为false,所以阻塞
}
}
上面程序执行后,抛出异常,程序不会运行结束。
public class test3 {
public static void main(String[] args){
Thread main = Thread.currentThread();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程开始睡觉");
try {
Thread.sleep(3000);//睡一下保证是在main线程sleep后,才去中断main线程
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"抛出了中断异常");
}
System.out.println("子线程睡醒了,开始中断main线程");
main.interrupt();
}
}).start();
try {
System.out.println("主线程开始睡觉");
Thread.sleep(5000); //main线程开始睡觉
// 当被中断唤醒后,会消耗掉中断状态。唤醒后继续执行
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"抛出了中断异常");
}
LockSupport.park(); //消耗掉permit后,直接返回了
LockSupport.park(); //此时permit为0,中断状态为false,必然会阻塞
}
}
上面程序执行后,抛出异常,程序不会运行结束。
这个程序同上,只是之前的版本都是先中断,再sleep。现在保证是,main线程先sleep后,再去中断main线程。
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
Object lock = new Object();
synchronized (lock) {
try {
lock.wait(); //消耗掉中断状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
LockSupport.park(); //消耗掉permit
LockSupport.park(); //此时permit为0,中断状态为false,必然会阻塞
}
}
上面程序执行后,抛出异常,程序不会运行结束。
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
Thread thread = new Thread(()->{
while (true) {}
});
thread.start();
try {
thread.join(); //消耗掉中断状态
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park(); //消耗掉permit
LockSupport.park(); //此时permit为0,中断状态为false,必然会阻塞
}
}
上面程序执行后,抛出异常,程序不会运行结束。通过Dump Threads后,可以发现main处于WAITING (parking)
状态,即阻塞状态。
park
调用后一定会消耗掉permit
,无论unpark
操作先做还是后做。中断状态
为true,那么park
无法阻塞。unpark
会使得permit
为1,并唤醒处于阻塞的线程。interrupt()
会使得中断状态
为true,并调用unpark
。sleep() / wait() / join()
调用后一定会消耗掉中断状态
,无论interrupt()
操作先做还是后做。关于这一点,“如果中断状态
为true,那么park
无法阻塞”。在AQS源码里的acquireQueued
里,由于acquireQueued
是阻塞式的抢锁,线程可能重复着 阻塞->被唤醒 的过程,所以在这个过程中,如果遇到了中断,一定要用Thread.interrupted()
将中断状态
消耗掉,并将这个中断状态
暂时保存到一个局部变量中去。不然只要遇到中断一次后,线程在抢锁失败后却无法阻塞了。