java并发编程基础(三)-终结任务
本文为学习《thinking in java》第21章的相关笔记
线程结束任务
-
样例
class Worker implements Runnable{ private static volatile boolean canceled = false; // static静态使得多个线程都共享 public static void cancel(){ // 设置停止标记 canceled = true; } @Override public void run() { while (!canceled) { // do something } } }
使用ExecutorService启动任务后,当我们想停止线程的执行时,就可以使用以下语句
Worker.cancel(); executorService.shutdown();
阻塞
- 忙等待和阻塞
- 前者占用CPU,后者不占用
- 线程进入阻塞状态
- 调用sleep方法
- 调用wait方法
- 任务等待IO完成
- 任务试图获取对象锁,但是对象锁不可用
中断
-
Thread.interrupt()
-
Executer.shutdownNow()
- shutdown(): 线程池状态变为SHUTDOWN状态,不再往线程池中添加任务,但是线程池不会立刻退出,直到之前的任务处理完成才会退出
- shutdownNow(): 线程池立刻变为STOP状态,并试图停止所有正在执行的线程,不再不处理还在线程池中等待的任务。其实是通过Thread.interupt来实现的。但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
- 上述解释都只是参考网上博文,并不是书本内容
-
有时候你希望只是停止一个任务,可以使用submit()而不是execute()来启动任务,submit()返回泛型
Future<?>
,可以调用cancel()来中断该任务。如果将true传递给cancel(),那么它就会拥有在该线程上调用interrupt的权限 -
经过书本实验证明,IO和在synchronized块上的等待都是不可中断的,而sleep()的方法是可以中断的。但是这种IO阻塞的问题,只可以通过关闭任务在其发生阻塞的底层资源,比如一个监听ServerSocket的InputStream的线程,可以通过关闭这个InputStream来中断线程的阻塞
-
幸运的是,被阻塞的nio通道会自动响应中断。当然,同理,你可以可以直接关闭底层资源。
被互斥所阻塞
-
同一个互斥可以被同一个任务多次获得
public class MultiLock { public synchronized void f1(int count){ if (count-- > 0){ System.out.println("f1 is ready to call f2 with the curr count is " + count); f2(count); } } public synchronized void f2(int count) { if (count-- > 0){ System.out.println("f2 is ready to call f1 with the curr count is " + count); f1(count); } } public static void main(String[] args) { final MultiLock multiLock = new MultiLock(); new Thread(){ @Override public void run() { multiLock.f1(10); } }.start(); } }
只要是在同一个任务获得对象,f1()和f2()就不会互斥
-
在ReentranLock上阻塞的任务具备可以被中断的能力,这和在synchronized方法上或者临界区上阻塞的任务完全不同
class BlockedMutex { private Lock lock = new ReentrantLock(); public BlockedMutex(){ lock.lock(); } public void f(){ try { lock.lockInterruptibly(); System.out.println("lock acquired in f()!"); } catch (InterruptedException e) { System.out.println("Interrupted from lock acquisition in f()!"); } } } class Blocked2 implements Runnable{ BlockedMutex blockedMutex = new BlockedMutex(); @Override public void run() { System.out.println("waiting for f() in BlockedMutex"); blockedMutex.f(); System.out.println("Broken out of blocked call"); } } public class Interrupting2 { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Blocked2()); thread.start(); TimeUnit.SECONDS.sleep(1); System.out.println("Issuing thread.interrupted()"); thread.interrupt(); } }
BlockedMutex类有一个构造器,它获取创建对象上自身的lock且不释放它。如果试图从第二个任务中调用f(),那么将会是因为Mutex不可获得而被阻塞。故在Blocked2中,run方法总是在调用blockedMutex.f()时被阻塞。但是本次程序使用的是lock.lockInterruptbly(), 它是可以响应中断的。这正是lock()和lockInterruptbly()区别
- lock()优先获得锁,先获得锁再响应中断
- lockInterruptbly()优先考虑响应中断
检查中断
-
在线程上调用interrupt()时,中断发生的唯一时刻是任务要进入到阻塞操作中,或者已经在阻塞操作内部(除了不可中断的IO和synchronized方法外)
-
可以通过调用interrupted()来检查中断状态,不仅可以知道interrupt是否被调用过,而且还可以清除中断状态
class NeedsCleanup{ private final int id; public NeedsCleanup(int id) { this.id = id; System.out.println("Needs clean up " + id); } public void cleanup(){ System.out.println("cleaning up " + id); } } class Blocked3 implements Runnable { private volatile double d = 0.0; @Override public void run() { try { while (!Thread.interrupted()) { // point 1 NeedsCleanup n1 = new NeedsCleanup(1); try { System.out.println("sleeping"); TimeUnit.SECONDS.sleep(1); //point 2 NeedsCleanup n2 = new NeedsCleanup(2); try { System.out.println("calculating"); for (int i = 1; i < 2500000; i++) d = d + (Math.PI + Math.E) / d; System.out.println("finished time-consuming operation"); } finally { n2.cleanup(); } } finally { n1.cleanup(); } } System.out.println("exiting via while() test"); } catch (InterruptedException e) { System.out.println("exiting via InterruptedException"); } } } public class InterruptingIdiom { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Blocked3()); thread.start(); TimeUnit.MILLISECONDS.sleep(1050); // 调值 thread.interrupt(); } }
尝试运行上述代码,并在我注释调值处尝试不同的睡眠时间,可以发现
- 如果Interrupt()的调用在注释point2之后,即在非阻塞的操作过程中被调用,那么会首先继续计算直到计算结束,n1和n2调用完cleanup(),然后在while的顶部退出循环
- 如果Interrupt()的调用在注释point1和point2之间(在while语句之后,但在sleep()之前或者其过程中)被调用,那么任务就会经由InterruptedException退出