启动和终止线程
构造线程
在运行线程之前首先要构造一个线程对象,线程对象在构造的时候需要提供所需要的属性,如线程所属的线程组、线程优先级、是否是Daemon线程等信息。代码清单所示的代码摘自java.lang.Thread中对线程进行初始化的部分。
Thread.java
private void init(ThreadGroup g,Runnable target,String name,long statckSize,AccessControlContext acc){
if(name == null){
throw new NnllPointerException("name cannot be null");
}
//当前线程就是该线程的父线程
Thread parent = currentThread();
this.group = g;
//将Daemon、priority属性设置为父线程的对应属性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.name = name.toCharArray();
this.target = target;
setPriority(priority);
//将父线程的InheritableThreadLocal 复制过来
if(parent.inheritableThreadLocals != null)
this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.
inheritableThreadLocals);
//分配一个线程ID
tid = nextThreadID();
}
在上述过程中,一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Deamon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这child线程。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。
启动线程
线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启用调用start()方法的线程。
注意:启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程序或者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字
理解中断
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其它线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。
线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted() 来进行判断是否被中断,也可以调用静态方法Thread.interrupted() 对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted() 时依旧会返回false。
从Java 的 API 中可以看到,许多声明抛出 InterruptedException()的方法
(例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException 之前,Java 虚拟机会先将该线程的中断标识位清除,然后抛出 IntrruptedException,此时调用 isInterrupted()方法将会返回 false 。
在代码清单的例子中,首先创建两个线程,SleepThread和BusyThread,前者不停地睡眠,后者一直运行,然后对这两个线程分别进行中断操作,观察二者的中断标志位。
Interrupted.java
public class Interrupted {
public static void main(String[] args){
//SleepThread 不停的尝试睡眠
Thread sleepThread = new Thread(new SleepRunner(),"SleepThread");
sleepThread.setDaemon(true);
//busyThread 不停的运行
Thread busyThread = new Thread(new BusyRunner(),"BusyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
// 休眠5秒,让sleepThread和busyThread充分运行
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
// 防止sleepThread和busyThread立刻退出
SleepUtils.second(2);
}
static class SleepRunner implements Runnable{
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class BusyRunner implements Runnable{
@Override
public void run() {
while(true){
}
}
}
}
输出结果如下:
SleepThread interrupted is false
BusyThread interrupted is true
从结果可以看出,抛出InterruptedException的线程SleepThread,其中断标志位被清除了,而一直忙碌运作的线程busyThread,中断标识位没有被清除。
安全的终止线程
上面提到的中断状态是线程的一个标志位,而中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。除了中毒案之外,还可以利用一个Boolean 变量来控制是否需要停止任务并终止线程
下面的例子,创建了一个线程CountThread,它不断地进行变量累加,而主线程尝试对其进行中断操作和停止操作。
public class Shutdown{
public static void main(String[] args){
Runner one = new Runner();
Thread countThread = new Thread(one,"CountThread");
countThread.start();
//睡眠1秒,main线程对CountThread进行中断,使CountThread 能够感知中断而结束
TimeUnit.SECOND.sleep(1);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two,"CountThread");
countThread.start();
//睡眠1秒,main线程对Runner two 进行取消使 CountThread 能够感知on位false 而结束
two.cancel();
}
private static class Runner implements Runable{
private long i;
private volatile boolean on = true;
public void run(){
while(on && !Thread.currentThread.isInterrupted()){
i++l
}
System.out.println("Count i = " + i);
}
public void cancel(){
on = false;
}
}
}
执行结果如下所示(输出内容可能不同)
Count i = 448651771
Count i = 464619438
实例在执行过程中,main线程通过中断操作和cancel() 方法均可使CountThread 得以终止。这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种线程的做法显得更加安全和优雅