Thread中断解析


线程中的中断, 并不是字面上的意思就像stop那样粗暴的结束线程. 这里的中断只是一个标记, 设置中断后会在线程内部标记一下有人要中断自己了, 至于接下来要做什么, 是结束线程的运行, 是开始运行其他的任务还是运行另外一个分支的逻辑? 就需要结合自己的情况自己来处理了. 还有一些场景会在设置中断后自动抛出异常, 下面会把所有的情况都捋一遍.

中断相关的方法

方法描述
interrupt()中断被调用线程, 设置中断标记
isInterrupted()判断被调用线程是否设置了中断, 只是判断状态并不会重置中断
interrupted()判断当前线程是否设置了中断, 如果设置了就重置中断状态

下面的demo展示了: 启动一个线程, 线程中使用循环等待5s(不要用sleep, 中断后会抛异常出来). 在线程等待的同时, 在主线程中断子线程. 再观察线程的中断状态和日志.

final String tag = "interruptedStatus";
final Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        long time = System.currentTimeMillis();
        log(tag, "thread is running 5s");
        while ((System.currentTimeMillis() - time < 5000)) {
            // unused
        }

        log(tag, "isInterrupted " + Thread.currentThread().isInterrupted());
        log(tag, "isInterrupted " + Thread.currentThread().isInterrupted());
        log(tag, "interrupted " + Thread.interrupted());
        log(tag, "interrupted " + Thread.interrupted());
    }
});

thread.start();
thread.interrupt();

根据方法的描述, 在设置中断状态后isInterrupted方法只会返回线程的中断状态, 所以多次调用isInterrupted的结果都是一致的. 而调用interrupted方法会消费中断的状态, 并重置为未中断状态, 所以第一次调用interrupted时线程为中断状态, 再次调用interrupted就为false了.

interruptedStatus, thread is running 5s
interruptedStatus, isInterrupted true
interruptedStatus, isInterrupted true
interruptedStatus, interrupted true
interruptedStatus, interrupted false

中断非阻塞时的线程

启动一个线程, 线程中循环等待2s(这里不要用sleep)直到检测到线程被中断了才结束线程. 主线程启动子线程后等待3s再中断子线程.

final String tag = "interruptRunning";
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            long time = System.currentTimeMillis();
            log(tag, "thread is running");
            while ((System.currentTimeMillis() - time < 2000)) {
                // unused
            }
        }
        log(tag, "thread end");
    }
});

thread.start();
Thread.sleep(3000);
log(tag, "interrupt thread");
thread.interrupt();

运行结果:

interruptRunning, thread is running
interruptRunning, thread is running
interruptRunning, interrupt thread
interruptRunning, thread end

中断特定阻塞时的线程

当中断正在特定阻塞状态下的线程时(例如 sleep, wait, join), 会从阻塞处抛出InterruptedException, 并且重置中断状态. 根据上面的代码修改为使用sleep等待, catch异常后log出中断状态并再自己设置一次中断, 这样才能退出循环.

final String tag = "interruptWaiting";
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                log(tag, "thread is running");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log(tag, "InterruptedException " + Thread.currentThread().isInterrupted());
                // 线程抛出异常后 会消费掉中断标记
                log(tag, "interrupt thread inner");
                Thread.currentThread().interrupt();
            }
        }
        log(tag, "thread end");
    }
});

thread.start();
Thread.sleep(2000);
log(tag, "interrupt thread");
thread.interrupt();

根据描述, 在catch处日志应该为false, 自己再次设置中断状态才让线程停止运行, 否则会继续循环运行. 运行结果:

interruptWaiting, thread is running
interruptWaiting, thread is running
interruptWaiting, interrupt thread
interruptWaiting, InterruptedException false
interruptWaiting, interrupt thread inner
interruptWaiting, thread end

中断等锁阻塞时的线程

当线程正阻塞在申请获取线程锁时(synchronized, ReentrantLock.lock等), 中断线程后只是设置中断标记并不像sleep那样抛出异常. 而一些有超时的等锁例如ReentrantLock.tryLock(long, TimeUnit)会抛出异常.

demo中新建一个公共属性用来加锁, 线程1加锁并用sleep持有锁5s, 线程2加锁并打印中断状态. 主线程中先启动线程1再启动线程2, 等待2s后中断线程2, 再等待1s后中断线程1.

final String tag = "interruptBlocking";
final Object lock = new Object();

Thread thread1 = new Thread() {
    public void run() {
        synchronized (lock) {
            try {
                log(tag, "thread1 hold lock 5s");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                log(tag, "thread1 InterruptedException");
            }
        }
    }
};
Thread thread2 = new Thread() {
    public void run() {
        synchronized (lock) {
            log(tag, "thread2 hold lock");
            log(tag, "thread2 isInterrupted " + Thread.currentThread().isInterrupted());
        }
    }
};

thread1.start();
thread2.start();
Thread.sleep(2000);
log(tag, "interrupt thread2");
thread2.interrupt();
// 中断阻塞的线程2后发现线程2还是处于阻塞状态
Thread.sleep(1000);
log(tag, "interrupt thread1");
// 中断线程1后, 线程1释放锁. 线程2正常运行
thread1.interrupt();

结合分析和代码, 在中断线程2时不会打印任何日志, 只有在中断线程1 -> 线程1释放锁 -> 线程2得到锁后才会打印线程2是中断状态的日志.

interruptBlocking, thread1 hold lock 5s
interruptBlocking, interrupt thread2
interruptBlocking, interrupt thread1
interruptBlocking, thread1 InterruptedException
interruptBlocking, thread2 hold lock
interruptBlocking, thread2 isInterrupted true

中断IO阻塞时的线程

当线程在进行IO操作或者等待接口请求返回时, 如果正在操作的对象实现了InterruptibleChannel, 那么设置中断后就会抛出ClosedByInterruptException异常. 否则的话就跟synchronized阻塞一样, 保持正常的运行流程, 只是设置中断标记. 如果操作对象没有实现特定的接口, 那么就跟中断非阻塞线程的情况一样了.

demo中启动一个线程, 线程内部使用FileChannel(实现了如果正在操作的对象实现了InterruptibleChannel)死循环写入数据到文件中, 如果有异常抛出则打印异常信息和中断状态并关闭IO退出线程.

final String tag = "interruptIO";
Thread thread = new Thread() {
    public void run() {
        FileChannel fileChannel = null;
        try {
            fileChannel = new FileOutputStream("test.txt", true).getChannel();
            while (true) {
                ByteBuffer byteBuffer = ByteBuffer.wrap("1111".getBytes("GBK"));
                fileChannel.write(byteBuffer);
            }
        } catch (Exception e) {
            log(tag, e.getClass().toString());
            log(tag, "isInterrupted " + Thread.currentThread().isInterrupted());
        } finally {
            IOUtil.close(fileChannel);
        }
    }
};

log(tag, "start thread");
thread.start();
Thread.sleep(2000);
log(tag, "interrupt thread");
thread.interrupt();

由于demo中使用的fileChannel是实现了InterruptibleChannel的, 所以主线程中断子线程后会抛出ClosedByInterruptException异常, 这里的异常跟InterruptedException有一些差异, 很多人都没有注意到. ClosedByInterruptException异常抛出后, 线程还是处于中断状态. 而InterruptedException抛出后会重置为非中断状态, 所以我们在catch处log发现当前线程还是处于中断状态的.

interruptIO, start thread
interruptIO, interrupt thread
interruptIO, class java.nio.channels.ClosedByInterruptException
interruptIO, isInterrupted true

转载请注明出处:https://blog.csdn.net/l2show/article/details/103580234

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值