Java并发(三)——终结任务
对于一般线程的退出通常可以选择设置一个标示位,并在程序运行中不断监测该标示位,如果标示位显示需要退出程序,则通过线程的return退出程序。当时如果程序因为阻塞(sleep,wait,输入输出操作或者synchronized)而停止运行时,标示位将无法被检验,在此时如果希望立刻终止程序,就必须使用中断。
但是在使用中断时,一般不建议对Thread对象直接调用interrupt方法来触发中断,而应通过Executor来执行所有操作。通过执行Executor的shutdownNow方法将发送一个interrupt调用给所有它启动的所有线程。而如果只希望给某一个线程发送interrupt,那么应该使用submit而不是execute来提交线程,此时就能够使用cancel(true)来实现对某一特性线程发送中断信号。
import java.util.concurrent.*; import java.io.*; class Print{ public static void print(String str){ System.out.println(str); } } class SleepBlocked implements Runnable{ public void run(){ try{ TimeUnit.SECONDS.sleep(100); } catch(InterruptedException e){ Print.print("InterruptedExecption"); } Print.print("Exiting SleepBlocked.run()"); } } class IOBlocked implements Runnable{ private InputStream in; public IOBlocked(InputStream is){ in = is; } public void run(){ try{ Print.print("Waiting for read():"); in.read(); } catch(IOException e){ if(Thread.currentThread().isInterrupted()) Print.print("Interruptd from blocked I/O"); else throw new RuntimeException(e); Print.print("InterruptedExecption"); } Print.print("Exiting IOBlocked.run()"); } } class SynchronizedBlocked implements Runnable{ public synchronized void f(){ while(true) // Never releases lock Thread.yield(); } public SynchronizedBlocked(){ new Thread(){ public void run(){ f(); // Lock acquired by this thread } }.start(); } public void run(){ Print.print("Trying to call f()"); f(); Print.print("Exiting SynchronizedBlocked.run()"); } } public class Interrupting{ private static ExecutorService exec = Executors.newCachedThreadPool(); static void test(Runnable r) throws InterruptedException{ Future<?> f = exec.submit(r); TimeUnit.MILLISECONDS.sleep(100); Print.print("Interrupting " + r.getClass().getName()); f.cancel(true); // Interrupts if running Print.print("Interrpt sent to " + r.getClass().getName()); } public static void main(String[] args) throws Exception{ test(new SleepBlocked()); test(new IOBlocked(System.in)); test(new SynchronizedBlocked()); TimeUnit.SECONDS.sleep(3); Print.print("Aborting with System.exit(0)"); System.exit(0); // ... since last 2 interrupts failed } }/* Output Interrupting SleepBlocked Interrpt sent to SleepBlocked InterruptedExecption Exiting SleepBlocked.run() Waiting for read(): Interrupting IOBlocked Interrpt sent to IOBlocked Trying to call f() Interrupting SynchronizedBlocked Interrpt sent to SynchronizedBlocked Aborting with System.exit(0) *///:~
从结果可以看出中断只能有效的终止sleep所造成的中断,但对于输入输出和synchronized所造成的中断无能为力。
对于IO操作所造成的中断,一种稍显笨拙但有时确实行之有效的方法是关闭任务在其上发生阻塞的底层资源。
import java.net.*; import java.util.concurrent.*; import java.io.*; public class CloseResource{ public static void main(String[] args) throws Exception{ ExecutorService exec = Executors.newCachedThreadPool(); ServerSocket server = new ServerSocket(8080); InputStream socketInput= new Socket("localhost", 8080).getInputStream(); exec.execute(new IOBlocked(socketInput)); exec.execute(new IOBlocked(System.in)); TimeUnit.MILLISECONDS.sleep(100); Print.print("Shutting down all threads"); exec.shutdownNow(); TimeUnit.SECONDS.sleep(1); Print.print("Closing " + socketInput.getClass().getName()); socketInput.close(); // Releases blocked thread TimeUnit.SECONDS.sleep(1); Print.print("Closing " + System.in.getClass().getName()); System.in.close(); // Releases blocked thread } }/* Output Waiting for read(): Waiting for read(): Shutting down all threads Closing java.net.SocketInputStream Interruptd from blocked I/O InterruptedExecption Exiting IOBlocked.run() Closing java.io.BufferedInputStream Exiting IOBlocked.run() *///:~
虽然synchronized方法,如果对象的锁已经被其他任务获得,那么调用任务将被阻塞,直至这个锁可获得。但是同一个互斥能够被同一个任务多次获得。
public class MultiLock{ public synchronized void f1(int count){ if(count-- > 0){ Print.print("f1() calling f2() with count " + count); f2(count); } } public synchronized void f2(int count){ if(count-- > 0){ Print.print("f2() calling f1() with count " + count); f1(count); } } public static void main(String[] args){ final MultiLock multiLock = new MultiLock(); new Thread(){ public void run(){ multiLock.f1(10); } }.start(); } }/* f1() calling f2() with count 9 f2() calling f1() with count 8 f1() calling f2() with count 7 f2() calling f1() with count 6 f1() calling f2() with count 5 f2() calling f1() with count 4 f1() calling f2() with count 3 f2() calling f1() with count 2 f1() calling f2() with count 1 f2() calling f1() with count 0 *///:~
最后对于ReentrantLock上阻塞的任务具备可以被中断的能力。
import java.util.concurrent.*; import java.util.concurrent.locks.*; class BlockedMutex{ private Lock lock = new ReentrantLock(); public BlockedMutex(){ // Acquire it right away, to demonstrate interruption // of a task blocked on a ReentrantLock: lock.lock(); } public void f(){ try{ // This will never be available to a second task lock.lockInterruptibly(); // Special call Print.print("lock acquried in f()"); } catch(InterruptedException e){ Print.print("Interrupted from lock acquisition in f()"); } } } class Blocked2 implements Runnable{ BlockedMutex blocked = new BlockedMutex(); public void run(){ Print.print("Waiting for f() in BlockedMutex"); blocked.f(); Print.print("Broken out of blocked call"); } } public class Interrupting2{ public static void main(String[] args) throws Exception{ Thread t = new Thread(new Blocked2()); t.start(); TimeUnit.SECONDS.sleep(1); Print.print("Issuing t.interrupt()"); t.interrupt(); } }/*Output Waiting for f() in BlockedMutex Issuing t.interrupt() Interrupted from lock acquisition in f() Broken out of blocked call *///:~
可以看出以上的所有方法都通过在阻塞调用上抛出异常来退出,但如果run()中碰巧没有产生任何阻塞调用的情况下,可以调用interrupted()来检查中断状态,这不仅可以告诉你interrupt()是否被调用过,而且还可以清除中断状态。如此就能够通过判断interruped()的返回值来实现run()中循环的退出。