中断线程
在其他线程中对目标线程调用interrupt
方法,目标线程需要反复检测自身状态是否是interruptted状态,如果是就立刻结束运行
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start();
Thread.sleep(1); // 暂停1毫秒
t.interrupt(); // 中断t线程
t.join(); // 等待t线程结束
System.out.println("end");
}
}
class MyThread extends Thread {
public void run() {
int n = 0;
while (! isInterrupted()) {
n ++;
System.out.println(n + " hello!");
}
}
}
在上述代码中,main线程通过对t线程调用interrupt()
方法中断线程t,但要特别注意的是,interrupt()
方法仅仅是发出来"中断请求",具体能否响应中断,还是要看具体代码
如上述代码中while循环会检查isInterrupted()
状态,在响应后”interrupt请求“后能使得run()
立即结束
再看下面一段代码
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start();
Thread.sleep(1000);
t.interrupt(); // 中断t线程
t.join(); // 等待t线程结束
System.out.println("end");
}
}
class MyThread extends Thread {
public void run() {
Thread hello = new HelloThread();
hello.start(); // 启动hello线程
try {
hello.join(); // 等待hello线程结束
} catch (InterruptedException e) {
System.out.println("interrupted!");
}
hello.interrupt();
}
}
class HelloThread extends Thread {
public void run() {
int n = 0;
while (!isInterrupted()) {
n++;
System.out.println(n + " hello!");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
}
}
}
如果线程处于等待状态,例如,t.join()会让main线程进入等待状态,此时,如果main线程中对线程t调用interrupt(),join()方法会立刻抛出InterruptedException,因此,目标线程只要捕获到join()方法抛出的InterruptedException,就说明有其他线程对其调用了interrupt()方法,通常情况下该线程应该立刻结束运行。
在t线程结束前,对hello线程也进行了interrupt()调用通知其中断。如果去掉这一行代码,可以发现hello线程仍然会继续运行,且JVM不会退出。
另一个常用的中断线程的方法是设置标志位。我们通常会用一个running标志位来标识线程是否应该继续运行,在外部线程中,通过把HelloThread.running置为false,就可以让线程结束:
public class Main {
public static void main(String[] args) throws InterruptedException {
HelloThread t = new HelloThread();
t.start();
Thread.sleep(1);
t.running = false; // 标志位置为false
}
}
class HelloThread extends Thread {
public volatile boolean running = true;
public void run() {
int n = 0;
while (running) {
n ++;
System.out.println(n + " hello!");
}
System.out.println("end!");
}
}
helloThred的标志位running
是一个线程间共享的变量,用关键字volatile
修饰。
为什么要用volatile
关键字呢?
这与JAVA的内存模型相关。在JVM虚拟机中,变量的值保存在主内存中,当线程访问变量时,它会先获取一个副本,并保存在线程的工作内存中,如果修改了变量的值,JVM会在某个时刻把修改后的值写回到主内存,但这个时刻是不确定的。
如果不用volatile关键字就可能会导致各个线程中变量的值并不相同,
因此使用volatile关键字是告诉虚拟机:
- 每次访问变量时,总是获取主内存中最新的值;
- 每次修改变量后,总是立刻写回主内存;
volatile解决的是可见性问题:当一个线程修改了某个共享变量的值后,其他线程能立刻看到修改后的值。
守护线程
Java程序的入口是由JVM启动main线程,只有当main线程以及main中启动的其他线程都结束时,JVM才会退出,程序才会结束。
但如果有些线程的目的就是无限循环,例如,定时触发任务的线程,如果这个线程不结束,那么JVM就一直不会退出。
为了解决这种问题,我们需要引入守护线程
守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,JVM都会退出。
创建守护线程
Thread t = new MyThread();
t.setDaemon(true);
t.start();
创建守护线程的方法和创建普通线程基本一样,只是需要在启动线程前加上t.setDaemon(true);
特别注意守护线程不能持有任何需要关闭的资源,例如打开的文件等,因为JVM退出时,守护线程没有机会关闭文件,这会导致数据丢失。