线程的状态
一个线程会有如下五个状态
1.新建:线程在被创建时会暂时处于这种状态,此时系统为线程分配资源并对其进行初始化
2.就绪:此时线程已经可以运行,只是系统没有为其分配CPU时间。
3.运行:系统为线程分配了CPU时间,线程处于运行状态
4.阻塞:线程由于等待I/O等原因无法继续运行,等待结束后就又进入就绪状态了,阻塞时系统不会再为其分配CPU时间。
5.死亡:线程执行完所有的代码,此时线程不不可以再调度
上面五种状态中,只有在运行和阻塞状态时才有被终结的机会,其它状态时都无法终结。
在运行时终结
在运行状态时终结有一个最简单粗暴的办法,前面我们也使用过这种例子:
定义全局变量:
volatile boolean run = true;
线程一执行如下代码:
while(run) { //Do something}
这时在另一个线程中将run的值设置为false,线程一再检查run值的时候就不再继续运行了。
除了这种简单粗暴的方法之外,还可以使用中断信号来停止正在运行的线程。中断信号是一个线程发送给另一个线程的信号,这个信号告诉接收线程:你应该尽快停止运行。注意这只是一个信号而不是命令,接收方可以选择“听从劝告”,也可以选择“一意孤行”。前面我们讲线程池的时候有说过当调用shutdownNow()方法时,会向线程池中所有(注意是所有)线程发送一个中断信号,但是如果我只想对某个线程发送中断信号该怎么处理呢?其实我们也可以使用submit()方法返回的Future对象来发送中断信号,具体代码如下:
class InterruptableThread implements Runnable { private int value; public void run() { Thread currentThread = Thread.currentThread(); while(!currentThread.isInterrupted()) { value++; } System.out.println("Interrupted value = "+value); }}class AlwaysRunThread implements Runnable { public void run() { while(true) { System.out.println("Running"); } }}public class InterruptRunningTest { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); Future interruptableFuture= exec.submit(new InterruptableThread()); Future alwaysRunFuture= exec.submit(new AlwaysRunThread()); exec.shutdown(); interruptableFuture.cancel(true); alwaysRunFuture.cancel(true); }}
输出结果如下,此处省略无数个Running。
Running
Interrupted value = 12731
Running
Running
...
代码中创建了两个线程,第一个线程调用Thread类的currentThread()方法获得当前线程的对象,然后进入循环,每次循环都检查当前线程是否收到了中断信号,如果收到中断信号就中止循环。第二个线程定义了一个死循环,没有检查是否收到中断信号。我们在主线程中获得了两个线程的Future类,并调用cancel(true)方法向两个线程发送中断信号。根据结果我们可以看出第一个线程收到了中断信号并退出了;而第二个线程始终在运行,因为它没有检查是否有中断信号,即忽略了中断信号。这种让线程自己决定什么时候退出的机制是合理的,在收到信号后线程可以根据自己的需要释放持有的资源,如果粗暴的将线程中止可能发生内存泄露、死锁等问题。
在阻塞时终结
造成线程阻塞的原因有如下三种:
1. 线程内部调用了Thread.sleep()方法使线程进入休眠状态或者调用Object.wait()使线程被挂起
2. 线程在等待I/O
3. 线程在试图获取锁的时候,锁已经被其它线程获取,这时线程会被阻塞。
我们对处于这三种阻塞的线程发送中断信号,看看其反应:
class SleepBlockedThread implements Runnable { public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { System.out.println("SleepThread interrupted"); } }}class IOBlockedThread implements Runnable { public void run() { try { InterruptBlockedThread.input.read(); } catch (IOException e) { } System.out.println("IOThread interrupted"); }}class LockBlockedThread implements Runnable { private Lock lock; LockBlockedThread(Lock lock) { this.lock = lock; } public void run() { lock.lock(); System.out.println("LockThread interrupted"); }}public class InterruptBlockedThread { public static InputStream input; public static void main(String[] args) throws Exception { new ServerSocket(8010); input = new Socket("localhost", 8010).getInputStream(); ExecutorService exec = Executors.newCachedThreadPool(); exec.submit(new SleepBlockedThread()); exec.submit(new IOBlockedThread()); Lock lock = new ReentrantLock(); lock.lock();//先在主线程里获取锁 exec.submit(new LockBlockedThread(lock)); exec.shutdownNow(); }}
输出结果如下,并且程序始终没有退出:
SleepThread interrupted
从运行结果我们可以看出只有当线程被挂起时才可以被中断信号终结,其它两种情况都不能终结。但是我们可以通过其它办法将其终结。IO阻塞可以关闭让其阻塞的输入输出流,即干掉让其阻塞的根源;Lock阻塞可以调用lockInterruptibly()方法来替代lock()方法。具体代码如下:
class IOBlockedThread implements Runnable { public void run() { try { InterruptBlockedThread.input.read(); } catch (IOException e) { } System.out.println("IOThread interrupted"); }}class LockBlockedThread implements Runnable { private Lock lock; LockBlockedThread(Lock lock) { this.lock = lock; } public void run() { try { lock.lockInterruptibly(); } catch (InterruptedException e) { } System.out.println("LockThread interrupted"); }}public class InterruptBlockedThread { public static InputStream input; public static void main(String[] args) throws Exception { new ServerSocket(8010); input = new Socket("localhost", 8010).getInputStream(); ExecutorService exec = Executors.newCachedThreadPool(); exec.submit(new IOBlockedThread()); Lock lock = new ReentrantLock(); lock.lock();//先在主线程里获取锁 exec.submit(new LockBlockedThread(lock)); exec.shutdownNow(); input.close(); }}
输出结果如下:
LockThread interrupted
IOThread interrupted
由结果可以看出以上方法奏效了,这两个线程都被终结了。如果使用内置锁的时候被阻塞了,那么线程将无法被终结,感兴趣的读者可以自行测试。
总结
当线程处于运行状态时,可以向线程发送信号,同时线程每次循环都检查是否收到了信号,这样线程就可以自行退出。当线程处于阻塞状态时,只有线程被Thread.sleep()或者Object.wait()挂起的时候才可以接收中断信号。由于IO导致的阻塞可以通过关闭IO来实现,终结线程。显式锁可以通过调用lock.lockInterruptibly()方法来实现一个可中断的锁。内置锁和lock.lock()导致的阻塞是不能被终结的。
公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。