文章目录
原理简单讲解
首先声明,本文不会去贴native方法的cpp实现,而是以伪代码的形式来理解这些native方法。
- Thread对象的native实现里有一个成员代表线程的
中断状态,我们可以认为它是一个bool型的变量。初始为false。 - Thread对象的native实现里有一个成员代表线程是否可以阻塞的许可
permit,我们可以认为它是一个int型的变量,但它的值只能为0或1。当为1时,再累加也会维持1。初始为0。
调用park()与unpark()
park/unpark实现的伪代码
下面将以伪代码的实现来说明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。
park/unpark的实验
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.park(); //因为此时permit为0且中断状态为false,所以阻塞 } }
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.unpark(Thread.currentThread()); //置permit为1 LockSupport.park(); //消耗掉permit后,直接返回了 } }
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.unpark(Thread.currentThread()); 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(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,中断状态为false。 LockSupport.park(); //此时permit为0,中断状态为false,必然会阻塞 } }
interrupt()与park()
interrupt()实现的伪代码
interrupt(){
if(中断状态 == false) {
中断状态 = true; } unpark(this); //注意这是Thread的成员方法,所以我们可以通过this获得Thread对象 }
interrupt()会设置中断状态为true。注意,interrupt()还会去调用unpark的,所以也会把permit置为1的。
interrupt()实验
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(); //同上 } }
sleep()与interrupt()
sleep()实现的伪代码
sleep(){
//这里我忽略了参数,假设参数是大于0的即可 if(中断状态 == true) {
中断状态 = false; throw new InterruptedException(); } 线程开始睡觉; if(中断状态 == true) {
中断状态 = false; throw new InterruptedException(); } }
sleep()会去检测中断状态,如果检测到了,那就消耗掉中断状态后,抛出中断异常。但sleep()不会去动permit。
sleep()实验
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,必然会阻塞 } }
wait/join 效果同sleep
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()将中断状态消耗掉,并将这个中断状态暂时保存到一个局部变量中去。不然只要遇到中断一次后,线程在抢锁失败后却无法阻塞了。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/218375.html原文链接:https://javaforall.net
