Java多线程关于interrupt()的理解

Java的interrupt()方法并不直接中断线程,而是设置中断状态,用于通知线程。它能唤醒阻塞/睡眠状态的线程,抛出InterruptedException。线程需配合中断机制来结束自身。isInterrupted()在获取中断状态后会被重置,需要配合使用以避免小坑。

interrupt()并不会中断线程

当有道词典把interrupt翻译过来是中断的意思后,很多人就以为interrupt()是中断线程的意思。如果凭语义去认识这个方法,那估计你也会和我一样,浪费了好久的时间在钻牛角尖上。

先来看一段代码:

package com.adi.waitNInterrupt.demo01;

public class WaitInterupt {

    public static void main(String[] args) {
        Object o = new Object();
        WaitThread wt = new WaitThread(o);
        wt.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        wt.interrupt();
        System.out.println("interrupt~~~");
    }
}

class WaitThread extends Thread {
    Object o = null;

    public WaitThread(Object o) {
        this.o = o;
    }

    @Override
    public void run() {
        System.out.println("Thread is start !!");
        int time = 5;
        synchronized (o) {
            try {
                o.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        while (time -- > 0) {
            System.out.println("Thread is running !!");
        }
    }
}

如果按照原先的想法,程序应该不会打印“Thread is running”。
但实际情况如下:

Thread is start !!
interrupt~~~
Thread is running !!
Thread is running !!
Thread is running !!
Thread is running !!
Thread is running !!
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.adi.waitNInterrupt.demo01.WaitThread.run(WaitInterupt.java:36)

线程不仅被唤醒了,同时也抛出了一个InterruptedException

这里会被唤醒的原因,个人理解是interrupt()并不能中断线程,它只能中断处于阻塞/睡眠状态的线程,让处于阻塞/睡眠状态的线程重新进入运行状态。

interrupt()本质上是对于一个中断状态位进行设置,它其实更像是一种线程通知,而当目标线程收到了通知后是否对线程进行中断,这个是需要目标线程进行配合才能达到此种效果。

同时,假设我们的目的真的是想终止一个当前处于阻塞状态的线程,而目标线程非但没有被终止,反而进入了运行状态,这是否与我们程序员想要的结果有所违背了呢。而这,其实也是为什么会抛出InterruptedException的原因。假若你真的是要终止线程,那你就必须将上面的run方法做一下调整。

1、将线程终止的修改方案

@Override
public void run() {
    System.out.println("Thread is start !!");
    int time = 20;
    synchronized (o) {
        try {
            o.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return;
        }
    }
    while (time-- > 0) {
        System.out.println("Thread is running !!");
    }
}

2、在不违背interrupt()本意下的修改方案

@Override
public void run() {
    System.out.println("Thread is start !!");
    int time = 20;
    synchronized (o) {
        try {
            o.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
    while (time-- > 0) {
        System.out.println("Thread is running !!");
    }
}

此种方式并不能中断线程,但是它能还原调用了interrupt()时的一个状态位。也就是重新把中断位设置为了true,这样不会与原先的程序意思相违背。

3、线程相互配合的修改方案

@Override
public void run() {
    System.out.println("Thread is start !!");
    int time = 20;
    synchronized (o) {
        try {
            o.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
    while (!this.isInterrupted()) {
        System.out.println("Thread is running !!");
    }
}

第三种方案相对来说会更符合interrupt()的存在价值。线程的中断基于一个相互协调的通知机制,线程可以在中断之前将一些有必要处理的问题进行处理后退出。

但是这里的isInterrupted()方法会存在一个小坑。当你调用了它后,他会被重新设置为false。

public class IsInterruptTest {

    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("running");
                boolean tmp = isInterrupted();
                while (!tmp) {}
                System.out.println("after invoke interrupt(), first " + tmp);
            }
        };
        t.start();
        System.out.println("before invoke interrupt() " + t.isInterrupted());
        t.interrupt();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after invoke interrupt(), second " + t.isInterrupted());
    }
}

运行结果:

before invoke interrupt() false
running
after invoke interrupt(), first true
after invoke interrupt(), second false

isInterrupt()的小坑以及用意

当线程 t 的 run()方法里获取的中断标志是true,而主线程再次调用t.isInterrupted()方法获取的中断标记是false。

这里这么做其实也相当于说明了前面我们所说的,interrupt()是基于设置中断标志的通信方式的。
假若A线程调用了B线程的interrupt(),B线程获取到了标记,如果B线程的本意是想配合A线程去完成这个中断的,那它在获取到了中断标记为true 的时候就应该去停止线程了,而B如果不想停止的话,那就应该把中断标志还原,因为B确实是没被中断的。
假若B 会配合A 去终止的话,当获取到为true的时候,B就已经是正在走向died的一个状态,但此时线程可能还会有一些资源需要释放,存在着一个时间问题,那么此时,它确实也是还没真正死亡的,它只是处于一个走向died的运行状态,因此,调用了isInterrupt()后就有必要将中断标志重新设置为false。

jdk关于interrupt()的解释

Interrupts this thread. Unless the current thread is interrupting
itself, which is always permitted, the checkAccess method of this
thread is invoked, which may cause a SecurityException to be thrown.

If this thread is blocked in an invocation of the wait(), wait(long),
or wait(long, int) methods of the Object class, or of the join(),
join(long), join(long, int), sleep(long), or sleep(long, int), methods
of this class, then its interrupt status will be cleared and it will
receive an InterruptedException.

If this thread is blocked in an I/O
operation upon an InterruptibleChannel then the channel will be
closed, the thread’s interrupt status will be set, and the thread will
receive a ClosedByInterruptException.

If this thread is blocked in a
Selector then the thread’s interrupt status will be set and it will
return immediately from the selection operation, possibly with a
non-zero value, just as if the selector’s wakeup method were invoked.

If none of the previous conditions hold then this thread’s interrupt
status will be set. Interrupting a thread that is not alive need not
have any effect.

这里主要是说:

当调用interrupt()遇上了调用wait(), sleep(),join()这些方法使线程进入阻塞状态的线程,中断标志会被清除,并且抛出InterruptedException
这里刚好证明了上面的案例为什么会抛出异常。

如果此线程在InterruptibleChannel上的I / O操作中被阻止,则该通道将被关闭,该线程的中断状态将被设置,并且该线程将收到ClosedByInterruptException

如果此线程在选择器中被阻塞,则该线程的中断状态将被设置,并且它将立即从选择操作中返回,可能具有非零值,就像调用选择器的唤醒方法一样。

中断未激活的线程不会产生任何效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值