第五周学习心得
本周进行了《Java并发编程实战》第6、7章的学习,第6章主要通过围绕任务执行来设计应用程序,侧重于对Executor的使用。第7章主要讲了任务、线程和服务的取消与关闭操作带来的潜在问题以及如何避免这些问题。
任务执行——针对Executor的使用
-
在设计多线程应用程序时,需要明确任务的边界以及选择合理的任务执行策略,以便于更好的进行线程代码的独立编写。
大多数服务器应用程序都提供了一种自然的任务边界选择方式:以用户请求为任务边界。
-
无限制创建线程的不足:
- 线程生命周期的开销非常高。线程的创建与销毁需要时间和延迟处理的请求,频繁的创建线程会消耗大量的计算资源。
- 资源消耗。过多活跃的线程会消耗系统内存,给垃圾回收器带来压力。如果可运行的线程数量超过可用的处理器的数量,将会出现线程闲置的现象,并且频繁的切换线程上下文将产生其他的性能开销。
- 稳定性。操作系统对可创建的线程数量有着比JVM更为严格的约束,如果超出这些限制,将会出现OutOfMemoryError异常。
-
Executor基于生产者——消费者模式,将任务的提交与任务的实际执行解耦开来。Executor接口提供了四种常用的创建线程池的方法:
- newFixedThreadPool:创建一个固定长度的线程池,每提交一个任务时就创建一个新的线程,达到最大数量后将不再增加。如果某个线程发生异常结束,将会补充一个新的线程。
- newCachedThreadPool:可缓存的线程池,如果线程池的当前规模超出了处理需求将回收空闲的线程,需求增加时会添加新的线程,对线程的数量不做任何限制。
- newScheduledThreadPool:固定长度的线程池,以延迟或定时的方式执行任务。
- newSingleThreadExecutor:单线程的Executor。
取消与关闭
-
Java线程的中断机制是一种写作机制,并不是一种抢占式的强制中断机制,这种机制提供了更强的灵活性,使得线程可以自己提供更安全的中断策略。
-
thread.interrupt()方法会将线程的中断状态置为true。
-
thread.isInterrupted()方法能够返回目标线程的中断状态.
-
静态方法Thread.interrupted()方法会清除当前线程的中断状态,并返回状态改变之前的值。也是清除线程中断状态的唯一主动方法。
-
线程应该只能由其所有者进行中断,线程所有者可以将线程的中断策略信息封装到某个合适的取消机制中。
-
只有在实现了线程中断策略的代码才可以屏蔽中断请求,否则都不应该屏蔽中断请求。
-
任务的生命周期、处理异常以及实现取消可以使用Future来进行管理,Future是一种抽象的任务管理机制。
-
Future.cancel(Boolean mayInterruptIfRunning)方法,表示任务是否能够接收中断。如果参数为true并且任务当前正在某个线程中运行,那么这个线程就能被中断。参数为false,应该用于不处理中断的任务中。
//通过future来取消任务的例子 public static void timedRun(Runnable r, long timeout, TimeUnit unit) throws InterruptedException { Future<?> task = taskExec.submit(r); try { task.get(timeout, unit); } catch (TimeoutException e) { //应当取消任务 } catch (ExecutionException e) { // 重新抛出异常 throw launderThrowable(e.getCause()); } finally { //如果任务正在运行,那么将被中断;已经运行结束的任务不会有影响 task.cancel(true); } }
-
ExecutorService的shutdownNow()方法会立即执行关闭操作,并会返回已提交单尚未开始的任务。但是正在运行还未结束的任务需要在附加操作中进行获取。
-
关闭钩子:在正常关闭中,JVM首先调用所有已注册的关闭钩子,关闭钩子是指通过Runtime.addShutdownHook注册的但尚未开始的线程。对所有服务应当使用同一个关闭钩子,而不是每个服务使用一个不同的关闭钩子,否则会出现关闭操作之间出现竞态条件或者死锁等问题。
//通过注册一个关闭钩子来停止某个服务,将在JVM关闭之前进行调用关闭 public void close(){ Runtime.getRuntime().addShutdownHook(new Thread(){ public void run(){ try{ LogService.this.stop()} catch(InterruptedException e){} } }); }
-
避免使用终结器,大多数情况下使用finally代码块和显式的close() 方法能够比终结器更好的管理资源。