多线程并发实践_笔记_第八章

应用线程池

1.任务和执行策略之间的隐性耦合
《1》依赖性任务:当任务间相互独立时,线程池可任意改变长度和配置,当任务依赖于其他任务时则给执行策略带来约束

《2》采用线程限制的任务:如使用单线程化的executor能使得任务单线程执行,从而不是线程安全的资源也能保证同步,但是这样也就约束了执行任务必须单线程化(因为可能存在非线程的资源)

《3》对响应时间敏感的任务:当多个长时间的的任务提交给具有少量线程的线程池会导致服务的响应性受到影响(单线程化的executor更明显,同样道理)

《4》ThreadLocal:一个线程拥有一个该变量,但是线程池中的线程可能会被重复使用,导致可能在任务间传递

《5》 当任务是同类的,独立的时候,线程池才会有最佳表现(如web服务器等)

《6》线程饥饿死锁:所有的线程都在等待线程池中同一队列的其他任务,导致所有任务阻塞,成为线程饥饿死锁(单线程化的executor很容易出现)

《7》耗时操作:长时间的任务可能导致线程池的大部分线程处于使用中,使得响应性变坏。可以使用限时的阻塞方法来防止这一方法,如Future.get(time,unit)

2.定制线程池的大小:
《1》线程池的合理长度任务类型和部署系统的特征。因此应该由某种配置机制来提供。或者由vailableProcessors() (获取处理器的数量)决定
《2》对于计算密集型的任务,计算n个任务而线程池长度为n+1最佳(剩下一个线程保证有任务出错时及时补充)
《3》对于i/o或其他阻塞操作的任务,不是所有的线程都会在所有的时间被调度,因此应使用较大的长度,具体长度应根据任务阻塞和执行的时间的比例设置,给出一下定义:
  N=cpu数量, u=cpu使用率 ,w/c=等待时间和计算时间的比例
最优线程池大小= n*u*(1+w/c)
另外cpu周期并不是唯一可以使用线程池管理的资源,如数据库连接,文件句柄,sockect句柄,内存等也是
3.配置ThreadPoolExecutor
《1》线程的创建和销毁:
(1)核心大小:就是目标大小,线程池会视图维护线程数量在这大小之中,即使没有任务执行(等于核心大小的线程数量一开始就被创建,但是会等到任务被提交时这些线程才执行),并且在工作队列满之前,线程池都不会创建更多的线程。
(2)最大池大小:可同时活动线程的最高上限
(3)存货时间:线程处于闲置状态超过该时间后需要销毁。
(4)由于线程的销毁和创建同样需要开销,所以保持线程池在核心大小其实是一种折中,使得减少线程的创建和销毁,所以核心大小应该调节为使得线程池中的线程大部分时候处于应用中并且无需创建新的线程
(5)使用核心大小为零的线程池很危险,徐虽然会导致线程在执行完后销毁,但是任务队列满之前将不会开始执行任何任务,并且有可能引发一些奇怪的问题
《2》管理队列任务:
(1)executor管理的请求会在一个Runnable队列中等待,而不是作为竞争cpu的线程,使用队列管理任务消耗更为低(ThreadPoolExecutor允许提供一个队列作为任务队列)
(2)使用有限队列:避免内存的溢出,减少上下文切换,防止过载而收攻击,但是会导致潜在的吞吐量和响应性的问题。
(3)使用无限队列:如newFixedThreadPool或newSingleThreadPool,但是队列会无线的增加
(4)同步移交:如newCacheThreadPool(他是Executor的一个很好的默认选择,具有比定长队列更好的等候性能),他是有SynChronousQueue,绕开队列,SynChronousQueue并不是一个真正的队列,他是一个线程间移交信息的机制,每一个移交的任务都必须有一个相应的接受线程,当缺少这一个线程时,若小于最大池大小则创建一个,若等于的话,则会拒绝请求,因此当线程池是无限的或者可以接受任务被拒绝的时候才使用
(5)另外使用LinkedBlockingQueue或者ArrayBlockingQueue这样的先进先出队列,或者PriorityBlockingQueue优先级队列等可以控制任务执行的顺序。
(6)只有当任务彼此独立时才能使得有限的线程池工作合理因为相互依赖的任务在有限线程池中可能引起死锁。
《3》饱和策略:当一个有限队列充满后,饱和策略开始起作用,即使已关闭的ThreadPoolExecutor也是
(1)ThreadPoolExecutor的饱和策略可以通过setRejectedExcutionHandler来修改(包括:abortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy,默认为为abortPolicy)
(2)abortPolicy:当任务队列已满时,对于无法进入任务队列的新提交任务,会爆出RejectedExecutionException。
(3)DiscardPolicy策略会默认放弃无法进入队列的任务,DiscardOldestPolicy——遗弃最旧,也就是接下来将执行的任务将被遗弃。
(4)CallerRunsPolicy,调用者运行策略,也就是说,当线程池已满,并且任务队列已满时,对于提交的无法进入队列的任务,既不抛出异常也不放弃,而是转由调用线程去执行该任务(这也就会使得调用线程无法再提交新的任务)
(5)书上的列子,使用有限的线程池和同步移交(并不是真正的队列,SynchronousQueue)配合CallerRunsPolicy,使得当队列和线程池都满的情况下能够平缓的劣化(提交任务的线程由于执行任务而无法提交任务)。
(6)使用信号量和非限制队列来配置调用execute方法的平缓策略,使得任务在超过指定数量后等待再提交。
《4》线程工厂:线程池需要创建一个线程工厂,默认情况下是一个创建一个新的,非后台线程的工厂
(1)提供该工厂使得创建线程的方法被定制,如可以为创建的线程定义一个UncaughtExceptionHandler等
《5》构造后再定制ThreadPoolExecutor:
(1)如果ThreadPoolExecutor是通过Executors中除newSingleThreadExecutor方法以外创建的,那么大部分通过构造函数传递的菜蔬可以在创建后通过setter修改
(2)Executors中包括一个名为unconfigurableExecutorService方法,返回一个包装后的ExecutorService,只暴露executorService的方法,不能进行配置,采用这一方式可以保证该ThreadPoolExecutor不会被不信任代码修改参数。
《6》扩展ThreadPoolExecutor
(1)提供beforeExecute,afterExecute和temination方法给子类去覆写,从而扩展
(2)无论是抛出异常还是执行完成,任务执行完都会执行afterExecute(任务完成后抛出Error则不会执行)
(3)在before中抛出异常的话,不会往下执行任务。
(4)teminated方法在线程池完成关闭后调用,可用于回收资源
5.并行递归算法
(1)当每一个迭代批次独立,并且完成循环体中每个迭代工作的意义足够大至足以弥补管理一个新任务的开销时,这个循环顺序是适合并行化的(也就是并行必须是安全的,并且带来的效率高于创建新线程的开销?)
(2)将循环并行化用于递归中:互不影响的每次计算都由一个线程执行

例子:
(1)使用闭锁存放结果,长度为1的countdownlatch,主线程等待这一结果,在获得后关闭
(2)使用concurrentMap来存放已到达过得地点,保证不重复移动到已到达的地点(putifabsent)
(3)并行递归算法使得在多线程模式下对谜题进行广度遍历
(4)使用线程安全的AutomicInteger方法记录已执行的线程数,以便在没有找到结果时及时关闭。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值