构造线程
在运行线程之前首先要构造一个线程对象,线程对象在构造的时候需要提供线程所需要 的属性,如线程所属的线程组、线程优先级、是否是Daemon线程等信息。
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) { if (name == null) { throw new NullPointerException("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是否为Daemon、优先级和加载资源的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虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
如下的代码,首先创建了两个线程,SleepThread和BusyThread,前者不停地睡眠,后者一直运行,然后对这两个线程分别进行中断操作,观察二者的中断标识位。
public class Interrupted { public static void main(String[] args) throws Exception { // 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(); // 休眠5s,让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立刻退出 TimeUnit.SECONDS.sleep(2); } static class SleepRunner implements Runnable { @Override public void run() { while (true) { SleepUtils.second(10); } } } static class BusyRunner implements Runnable { @Override public void run() { while (true) { } } } } 复制代码
输出如下
SleepThread interrupted is false BusyThread interrupted is true 复制代码
从结果可以看出,抛出InterruptedException的线程SleepThread,其中断标识位被清除了, 而一直忙碌运作的线程BusyThread,中断标识位没有被清除
过期的suspend(),resume()和stop()
大家对于CD机肯定不会陌生,如果把它播放音乐比作一个线程的运作,那么对音乐播放做出的暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。
如下代码中,创建了一个线程PrintThread,它以1秒的频率进行打印,而主线程对其进行暂停、恢复和停止操作。
public class Deprecated { @SuppressWarnings("deprecation") public static void main(String[] args) throws Exception { DateFormat format = new SimpleDateFormat("HH:mm:ss"); Thread printThread = new Thread(new Runner(), "PrintThread"); printThread.setDaemon(true); printThread.start(); TimeUnit.SECONDS.sleep(3); // 将PrintThread进行暂停,输出内容工作停止 printThread.suspend(); System.out.println("main suspend PrintThread at " + format.format(new Date())); TimeUnit.SECONDS.sleep(20); // 将PrintThread进行恢复,输出内容继续 printThread.resume(); System.out.println("main resume PrintThread at " + format.format(new Date())); TimeUnit.SECONDS.sleep(3); // 将PrintThread进行终止,输出内容停止 printThread.stop(); System.out.println("main stop PrintThread at " + format.format(new Date())); TimeUnit.SECONDS.sleep(3); } static class Runner implements Runnable { @Override public void run() { DateFormat format = new SimpleDateFormat("HH:mm:ss"); while (true) { System.out.println(Thread.currentThread().getName() + " Run at " + format.format(new Date())); SleepUtils.second(1); } } } } 复制代码
输出如下(输出内容与执行的具体时间有关)
PrintThread Run at 17:34:36 PrintThread Run at 17:34:37 PrintThread Run at 17:34:38 main suspend PrintThread at 17:34:39 main resume PrintThread at 17:34:42 PrintThread Run at 17:34:42 PrintThread Run at 17:34:43 PrintThread Run at 17:34:44 main stop PrintThread at 17:34:45 复制代码
在执行过程中,PrintThread运行了3秒,随后被暂停,3秒后恢复,最后经过3秒被终止。
通过示例的输出可以看到,suspend()、resume()和stop()方法完成了线程的暂停、恢复和终 止工作,而且非常“人性化”。但是这些API是过期的,也就是不建议使用的。
不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资 源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结 一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会, 因此会导致程序可能工作在不确定状态下。
正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建 议使用的过期方法,而暂停和恢复操作可以用后面提到的等待/通知机制来替代。
安全的终止线程
中断状态是线程的一个标识位,而中断操作是一种简便的线程间交互 方式,而这种交互方式最适合用来取消或停止任务。除了中断以外,还可以利用一个boolean变 量来控制是否需要停止任务并终止该线程
如下,创建了一个线程CountThread,它不断地进行变量累加,而 主线程尝试对其进行中断操作和停止操作。
public class Shutdown { public static void main(String[] args) throws Exception { Runner one = new Runner(); Thread countThread = new Thread(one, "CountThread"); countThread.start(); // 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束 TimeUnit.SECONDS.sleep(1); countThread.interrupt(); Runner two = new Runner(); countThread = new Thread(two, "CountThread"); countThread.start(); // 睡眠1秒,main线程对Runner two 进行取消,使CountThread能够感知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; } } } 复制代码
输入如下
Count i = 543487324 Count i = 540898082