《Java并发实战》读书笔记(二)

第二部分 结构化并发应用程序

这一部分主要介绍如何利用线程来提高并发应用程序的吞吐量或响应性。

第6章 任务执行

并发,是为了更快速的为更多的用户提供服务。
在围绕任务来设计程序结构时,服务器应用程序应选择清晰任务的边界,以及明确的任务执行策略,使得程序有更高的并发性。
本章介绍了如何识别可并行执行的任务,以及如何在任务执行框架中执行他们。

6.1 在线程中执行任务

在执行任务时,应采取何种策略呢?
一种情况是,应用程序每次只接受一个请求,串行的执行各项任务,但无法提供高吞吐率或快速响应性的。
另一种情况,无条件的为每个任务创建一个线程,在正常负载下是可以提升并发性能的,但不足之处在于:

  • 创建与销毁都是高开销的活动;
  • 活跃的线程会消耗系统资源,尤其是内存;
  • JVM分配到的内存有限,数量过多的线程会导致内存溢出。

那有没有更好的策略呢?答案是Executor框架。

6.2 Executor框架

Executor框架基于生产者——消费者模式,将任务的生产与执行解耦。
  它提供了多种任务执行策略,并对线程的生命周期、统计信息收集等功能提供了支持。
  任务执行策略包括:
1.能有多少个任务并发?
2.在什么线程中执行任务?
3.按照什么顺序?
4.队列中有多少个任务在等待?
5.过载时,如何拒绝任务,拒绝哪一个?
6.在执行任务前或后,应该进行哪些动作?
Java类库提供了Executor的几种线程池实现。线程池中的线程位于工作队列中,每次执行完任务后将被再次重用。
这种队列重用机制的好处在于,避免了生产线程的巨大开销,并且在任务到达时不用等待。
类库提供的创建线程池的方法有以下几种:
1.newFixedThreadPool,规模固定,线程数量随着任务的提交而逐渐达到最大值。
2.newCachedThreadPool,规模无限制,线程不够时增加,规模过大时回收线程。
3.newSingleThreadPool,规模数量为1,这种单线程主要实现任务按(FIFO/LIFO/优先级)来执行。
4.newScheduledThreadPool,规模固定,而且以延迟或定时的方式来执行任务。
既然了解了线程池的创建,接下来就来看看怎么关闭它。
为了解决如何关闭线程池这个问题,Executor扩展了ExecutorService接口,提供了shutdown()和shutdownNow()来关闭线程池。
shutdown()是一种比较平滑的方法,调用后,executor将不再接受新任务,并等待池中所有任务执行完毕。
shutdownNow()比较粗暴,它将尝试取消所有运行中的任务,并不再启动队列中等待的任务。

第7章 取消与关闭

7.1 取消任务与线程

任务和线程启动起来是一种容易的事。可要使线程安全、快速、可靠的停下来,却不是件容易的事。Java没有提供使线程的停下来的机制,但却提供了中断机制,来协助线程停下来。那下面就来介绍,如何使任务和线程在执行完正常工作之前,利用中断机制提前结束。

任务的取消有多种原因:
a.用户请求取消
b.有时间限制的操作
c.应用程序事件
d.错误
e.关闭
当产生取消的需求时,中断是最合理的方式。
中断是一种协作机制,线程A可以通过中断,告诉线程B,请它在合适的时候停止当前工作。
每个线程都有一个boolean类型的中断状态,通过interrupt(),isinterrupted(),interrupted()方法来获取和设置它的值。当线程收到中断请求后,线程该做哪些工作,又该如何响应这个中断呢?
最合理的中断策略是某种形式的线程级取消或服务级取消:尽快退出,在必要时清理,并通知线程所有者该线程已退出。当然也可以是其他的中断策略,比如暂停服务或重新开始服务。
要响应中断,有两种实用的方法:
a.传递InterruptedException异常
b.恢复中断状态。

7.2 停止线程

对于持有线程的服务,只要服务的存在时间大于创建线程的方法的存在时间,那么就应该提供生命周期方法。
而ExecutorService就提供了管理生命周期的方法,如第6章所示。
另外一种关闭生产者——消
费者的方式是使用”毒丸“对象。

第8章 线程池的使用

8.1 在任务与执行策略之间的隐性耦合

依赖性任务:如果提交给线程池的任务需要依赖其他的任务,那么必须小心的维持这些执行策略以避免产生活跃性问题
使用线程封闭机制的任务:单线程的Executor改为线程池环境,那么将会失去线程安全性
对响应时间敏感的任务:这些任务要求时间敏感,那么选择的策略就不应该是单线程的或者少量线程的线程池.
使用ThreadLocal的任务:Executor中可以自由重用线程.那么ThreadLocal中的"私有"变量也就不安全了
故:只有当任务都是同类型的并且相互独立的,线程池的性能才能达到最佳.

8.1.1 线程饥饿死锁
死锁:如果任务依赖于其他任务,那么可能会产生死锁.只要线程池中的任务需要无限期的等待一些必须由池中其他任务才能提供的资源或条件.
8.1.2 运行时间较长的任务
造成长时间的阻塞,导致线程池的性能变得糟糕.
8.2 设置线程池的大小
池的理想大小取决于提交任务的类型以及系统的性能,通常根据处理器核的数量来动态调整。另外,还需要考虑任务是计算密集型,还是资源密集型。
对于计算密集型,任务会长时间的占用CPU,那么通常情况下会池大小会设置为核数量+1,一般都能实现最优利用率。
而对于资源密集型,由于阻塞的特性,线程并不会一直执行。这种情况下要正确设置池大小,必须要估算任务等待时间与执行时间的比值。

第9章 图形用户界面应用程序

略。这一章主要介绍写GUI程序的线程问题,由于工作中很少接触这一部分,所以先不作了解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值