在任何线程池中,现有线程在可能的情况下,都会被自动复用。
合理利用线程池能够带来三个好处
线程池的继承体系:
todo: 更改这个图(即不使用截图)

Executor是启动任务的首选方法,Executor将管理Thread对象,与命令设计模式一样(todo: ??还没有体会到??),他暴露了要执行的单一方法。 Executor提供了一种将“任务提交”与“任务执行”分离开来的机制(解耦)。
public class CachedThreadPool {
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool(); // 创建
for(int i=0;i<5;i++);
exec.execute(new Liftoff); // 执行
exec.shutdown();// 终止全部
}
}
对shutdown的调用可以防止新任务被提交给这个executor,当前线程(本例main线程)将继续运行在shutdown被调用之前提交的所有任务,这个程序将在executor的所有任务完成之后尽快退出。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize(线程池的基本大小)(核心线程数量):
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。
如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
maximumPoolSize(线程池最大大小):
线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。
值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
keepAliveTime(线程活动保持时间):
线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
当前线程数大于核心线程数,如果空闲时间已经超过了keepAliveTime,那该线程会销毁。todo: ??
TimeUnit(线程活动保持时间的单位):
可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
workQueue(任务队列):
用于保存等待执行的任务的阻塞队列。当达到corePoolSize的时候,就向该等待队列放入线程信息(默认为一个LinkedBlockingQueue),运行中的线程属性为:workers,为一个HashSet;我们的Runnable内部被包装了一层;这个队列默认是一个无界队列(你也可以设定一个有界队列),所以在生产者疯狂生产的时候,需要考虑如何控制的问题。
threadFactory(线程工厂):
用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常有帮助。
handler(饱和策略)(拒绝策略):
当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。
关于
workQueue可以选择以下几个阻塞队列
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。PriorityBlockingQueue:一个具有优先级得无限阻塞队列。即:todo: 和上面的区别??
四种
handler(饱和策略)(拒绝策略)
CallerRunsPolicy:只用调用者所在线程来运行任务。DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。DiscardPolicy:不处理,丢弃掉。RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。即:todo: ???和上面4个的区别
使用execute方法、submit 方法,分别对应的是Runnable、Callable,对应的陈述请移步文章线程和任务。
线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会无限循环获取工作队列里的任务来执行。
当提交一个新任务到线程池时,线程池的处理流程如下:
即:
todo: 修改这两张图为合适的图

调用线程池的shutdown或shutdownNow方法来关闭线程池,调用这两个方法后均会立即从方法中返回而不会阻塞等待线程池关闭再返回。
shutdown
RejectedExecutionException异常shutdownNow
shutdownNow调用时:
InterruptedException异常。isShutdown和isTerminaed
只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。
当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。
至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完(可能在没有堵塞的时候被突然中断),则可以调用shutdownNow。
通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用
taskCount:线程池需要执行的任务数量。completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。getActiveCount:获取活动的线程数。通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法,如:
protected void beforeExecute(Thread t, Runnable r) { }
newFixedThreadPool(int nThreads)
newCachedThreadPool()
newSingleThreadExecutor()
FixedThreadPoolserversocket、运行短任务如更新本地或远程日志的小任务或事件分发线程)SignalThreadExecutor提交多个任务会排队;并且SignalThreadExecutor提供了一种重要的并发保证,其他线程不会被并发调用,这会改变任务的加锁需求,例如:策略:使用SignalThreadExecutor来运行需要访问文件系统的大量线程,在这种方式下, 不需要再共享资源上处理同步(不会过度使用文件系统)Executor创建每一个类型的线程池,都额外含有一个构造方法,在常用构造方法的基础上添加了一个额外参数,即ThreadFactory threadFactory,例如newSingleThreadExecutor(ThreadFactory threadFactory)。
此外还有一些有趣的线程池:newWorkStealingPool() 、newSingleThreadScheduledExecutor() 、newScheduledThreadPool(int corePoolSize)
要想合理的配置线程池,就必须首先分析任务特性吗,根据不同类型的任务来选择不同的线程池。另外为了避免撑满系统内存,提高系统稳定性和预警能力,应当使用有界队列(有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点,比如几千)。
通过采取任务拆分、不同规模的线程池处理不同类型的任务、有界队列来提高效率。
可以从以下几个角度来进行分析任务特性:
针对不同特性的任务,可以采取的措施有:
CPU密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池(Ncpu:cpu个数)。
IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。
混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。
执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。
依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。
- Java编程思想 第四版 中文版 倒数第二章
- 聊聊并发(三)Java线程池的分析和使用
原文:https://www.cnblogs.com/cheaptalk/p/12549674.html