interrupt()中断对LockSupport.park()的影响

文章目录

  • 原理简单讲解
  • 调用park()与unpark()
    • park/unpark实现的伪代码
    • park/unpark的实验
  • interrupt()与park()
    • interrupt()实现的伪代码
    • interrupt()实验
  • sleep()与interrupt()
    • sleep()实现的伪代码
    • sleep()实验
    • wait/join 效果同sleep
  • 总结

原理简单讲解

首先声明,本文不会去贴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,如果线程阻塞,再将其唤醒。从实现可见,无论调用几次unparkpermit只能为1。

park/unpark的实验

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()与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();  //同上
    }
}

上面程序执行后,程序运行结束。
这个程序同上,只是之前的版本都是先中断,再park。现在保证是,main线程先阻塞后,再去中断main线程。

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,必然会阻塞
    }
}

上面程序执行后,抛出异常,程序不会运行结束。
这个程序同上,只是之前的版本都是先中断,再sleep。现在保证是,main线程先sleep后,再去中断main线程。

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()中断状态消耗掉,并将这个中断状态暂时保存到一个局部变量中去。不然只要遇到中断一次后,线程在抢锁失败后却无法阻塞了。

你可能感兴趣的:(Java)