Java的线程阻塞、中断及优雅退出方法详解
作者:chengmaoning
线程阻塞
一个线程进入阻塞状态的原因可能如下(已排除Deprecated方法):
sleep()
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象锁并没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
wait()
调用wait()/1.5中的condition.await()使线程挂起,直到线程获取notify()/notifyAll()消息,(或者在Java SE5中java.util.concurrent类库中等价的signal()/signalAll()消息),线程才会进入就绪状态;
wait()调用会释放当前对象锁(monitor),这样其他线程可以继续进入对象的同步方法。
另外,调用join()也会导致线程阻塞,因为源码中join()就是通过wait()实现的;
等待I/O;
class Demo3 implements Runnable throws InterruptedException{ private InputStream in; public void run(){ in.read(); } }
无法持有锁进入同步代码
进入同步代码前无法获取锁,比如试图调用synchronized方法,或者显示锁对象的上锁行为ReentrantLock.lock(),而对应锁已被其他线程获取的情况下都将导致线程进入阻塞状态;
注意:yield()并不会导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
线程中断
线程中断可以在线程内部设置一个中断标识,同时让处于(可中断)阻塞的线程抛出InterruptedException中断异常,使线程跳出阻塞状态。相比其他语言,Java线程中断比较特殊,经常会引起开发人员的误解。因为中断听起来高深复杂,实质原理上非常简单。
中断原理
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。
Java中断模型也是这么简单,每个线程对象里都有一个boolean类型的标识(不一定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是通过native方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。
中断相关的方法
方法 | 解释 |
public static boolean interrupted() | 测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。 |
public boolean isInterrupted() | 测试线程是否已经中断。线程的中断状态不受该方法的影响。 |
public void interrupt() | 中断线程,设置中断标识为为true。 |
其中,interrupt方法是唯一能将中断状态设置为true的方法。静态方法interrupted会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解,需要特别注意。
此外,类库中的有些类的方法也可能会调用中断,如FutureTask中的cancel方法,如果传入的参数为true,它将会在正在运行异步任务的线程上调用interrupt方法,如果正在执行的异步任务中的代码没有对中断做出响应,那么cancel方法中的参数将不会起到什么效果;
ExecutorService exec = Executors.newCachedThreadPool(); Futrue<?> f = exec.submit(new TaskThread()); f.interrupt();
又如ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt方法来中断线程,所以如果工作线程中正在执行的任务没有对中断做出响应,任务将一直执行直到正常结束。
ExecutorService exec = Executors.newCachedThreadPool(); for(int i=0;i<5;i++) exec.execute(new TaskThread()) exec.shutdownNow();
中断的处理
中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。某些线程非常重要,以至于它们应该不理会中断,而是在处理完抛出的异常之后继续执行,但是更普遍的情况是,一个线程将把中断看作一个终止请求,这种线程的run方法遵循如下形式:
public void run() { try { ... /* * 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上 * !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显 * 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。 */ while (!Thread.currentThread().isInterrupted()&& more work to do) { do more work } } catch (InterruptedException e) { //线程在wait或sleep期间被中断了 } finally { //线程结束前做一些清理工作 } }
上面是while循环在try块里,如果try在while循环里时,因该在catch块里重新设置一下中断标示,因为抛出InterruptedException异常后,中断标示位会自动清除,此时应该这样:
public void run() { while (!Thread.currentThread().isInterrupted()&& more work to do) { try { ... sleep(delay); //wait(delay); } catch (InterruptedException e) { Thread.currentThread().interrupt(); //重新设置中断标示 } } }
可中断阻塞
对于处于sleep,join等操作的线程,如果被调用interrupt()后,会抛出InterruptedException,然后线程的中断标志位会由true重置为false,因为线程为了处理异常已经重新处于就绪状态。
不可中断的操作,包括进入synchronized段以及Lock.lock(),inputSteam.read()等,调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。
对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)。
对于inputStream等资源,有些(实现了interruptibleChannel接口)可以通过close()方法将资源关闭,对应的阻塞也会被放开。
但是,你可能正使用Java1.0之前就存在的传统的I/O,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。
很幸运,Java平台为这种情形提供了一项解决方案,即调用阻塞该线程的套接字的close()方法。在这种情形下,如果线程被I/O操作阻塞,当调用该套接字的close方法时,该线程在调用accept地方法将接收到一个SocketException(SocketException为IOException的子异常)异常,这与使用interrupt()方法引起一个InterruptedException异常被抛出非常相似。
java.nio类库提供了更加人性化的I/O中断,被阻塞的nio通道会自动地响应中断,不需要关闭底层资源;
线程优雅退出
一般情况下,线程退出可以使用while循环判断共享变量条件的方式,当线程内有阻塞操作时,可能导致线程无法运行到条件判断的地方而导致一直阻塞下去,这个时候就需要中断来帮助线程脱离阻塞。因此比较优雅的退出线程方式是结合共享变量和中断。
thread = new Thread(new Runnable() { @Override public void run() { /* * 在这里为一个循环,条件是判断线程的中断标志位是否中断 */ while (flag&&(!Thread.currentThread().isInterrupted())) { try { Log.i("tag","线程运行中"+Thread.currentThread().getId()); // 每执行一次暂停40毫秒 //当sleep方法抛出InterruptedException 中断状态也会被清掉 Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); //如果抛出异常则再次设置中断请求 Thread.currentThread().interrupt(); } } } }); thread.start();
到此这篇关于Java的线程阻塞、中断及优雅退出方法详解的文章就介绍到这了,更多相关Java线程阻塞、中断及优雅退出内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!