【Java并发编程】线程中断机制(辅以常见案例)


本文由浅入深介绍了中断机制、中断的常见案例和使用场景。

1. 为什么需要

因为一些原因需要取消原本正在执行的线程。我们举几个栗子:

  • 假设踢足球点球时,A队前4轮中了4个球,B队前4轮只中了2个球,此时胜负已分,第5轮这个点球就不用踢了,此时需要停止A队的线程和B队的线程(共5个点球)。
  • 假设用户从网络上下载一个50M的文件,如果网络很慢,用户等的不耐烦,点了取消下载操作,此时需要停止下载的线程。

2. 如何理解

中断可以理解为线程的一个内部标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。

中断就是其他线程通过interrupt方法给该线程发一个信号,该线程收到信号后可以选择响应或不响应信号,中断它只是一种协商机制。响应信号通常会结束自身run()方法的执行退出线程,不响应则继续执行无任何影响。

线程通过检测自身是否被中断来进行响应,检测方法有2种,分别是public static boolean interrupted()public boolean isInterrupted(),静态的方法会清除中断标识位属性,非静态方法不会清除中断标识位属性。

3. 如何使用

3.1. 中断相关API

Thread源码中关于中断的方法:

public class Thread implements Runnable {
    
    
    
    // 仅仅只是设置中断状态
    public void interrupt() {
    
    
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
    
    
            Interruptible b = blocker;
            if (b != null) {
    
    
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
    
	// 会清除中断状态
	public static boolean interrupted() {
    
    
		return currentThread().isInterrupted(true);
	}
    
	// 不会清除中断状态
	public boolean isInterrupted() {
    
    
		return isInterrupted(false);
	}
    
    private native void interrupt0();
    
    private native boolean isInterrupted(boolean ClearInterrupted);
}

绘制成表格:

API 作用
public void interrupt() 中断this线程
public static boolean interrupted() 测试当前线程是否被中断,且线程的中断状态会被清除
public boolean isInterrupted() 测试this线程是否中断。 中断状态不会被这个方法影响

特别需要注意的是:静态interrupted方法会清除线程的中断状态。

3.2. 中断正常状态下的线程

中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。

public class Main extends Thread {
    
    
    @Override
    public void run() {
    
    
        int i = 0;
        while (!Thread.currentThread().isInterrupted()) {
    
    
            i++;
            System.out.println();
        }
        System.out.println("done");
    }
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t = new Main();
        t.start();
        t.sleep(1000);
        t.interrupt();
    }
}

main线程通过调用t.interrupt()中断t线程。但需要注意,interrupt()方法仅仅是给t线程发送了"中断请求",只是把t线程的标识属性设置为中断,至于t线程是否响应或处理该中断请求则要看t线程的具体代码。

这里,t线程在while中循环检测中断请求(isInterrupted()),如果检测到线程被中断则跳出while循环结束线程的run()方法。

3.3. 中断特殊状态下的线程

特殊状态其实在官网Thread中有好几种,这里只列举最常见的一种

如果线程

  • 被阻塞在Object类的wait()wait(long)wait(long, int)方法
  • 或被阻塞在Thread类的join()join(long)join(long, int)sleep(long)sleep(long, int)

如果此时调用线程的中断方法interrupt(),线程的中断状态会被清除且抛出InterruptedException异常。这种情况的一般处理方法是:捕获InterruptedException异常,然后重新设置中断状态,即调用Thread.currentThread().interrupt();

我们来看示例代码:

public class Main extends Thread {
    
    
    @Override
    public void run() {
    
    
        while (!Thread.currentThread().isInterrupted()) {
    
    
            try {
    
    
                // 模拟任务代码
                Thread.sleep(2000);
            } catch (InterruptedException e) {
    
    
                // ... 清理操作
                System.out.println(isInterrupted());//false
                // 重设中断标志位为true
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(isInterrupted());//true
    }

    public static void main(String[] args) {
    
    
        Main t = new Main();
        t.start();
        try {
    
    
            Thread.sleep(100);
        } catch (InterruptedException e) {
    
    
        }
        t.interrupt();
    }
}

4. 如何安全的停止线程

前面提到中断状态只是线程的一个标识位,而中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程。 一般的,可以用以下方式来安全的停止线程:

  • 中断方式
  • volatile修饰的boolean变量方式

代码举例如下:

public class Shutdown {
    
    
	public static void main(String[] args) throws Exception {
    
    
		Runner one = new Runner();
		Thread t = new Thread(one, "t");
		t.start();
		// 睡眠1秒,让Runner线程充分运行,随后能够感知中断而结束
		TimeUnit.SECONDS.sleep(1);
		t.interrupt();
        
        
		Runner two = new Runner();
		t = new Thread(two, "t");
		t.start();
		// 睡眠1秒,让Runner线程充分运行,随后能够感知on为false而结束
		TimeUnit.SECONDS.sleep(1);
		two.cancel();
	}

	private static class Runner implements Runnable {
    
    
		private long i;

		private volatile boolean on = true;

		@Override
		public void run() {
    
    
			while (on && !Thread.currentThread().isInterrupted()) {
    
    
				i++;
			}
			System.out.println("Count i = " + i);
		}

		public void cancel() {
    
    
			on = false;
		}
	}
}

5. 参考资料

《Java并发编程的艺术》,可联系作者无套路下载可复制的电子版pdf书籍

官方Thread类的源码和注释

猜你喜欢

转载自blog.csdn.net/yuchangyuan5237/article/details/132252081
今日推荐