标签:开始 进阶 基于 art 传递 属性 repo 执行 shadow
?上篇《Java线程的6种状态详解及创建线程的4种方式》
前言:我们都知道,线程是稀有资源,系统频繁创建会很大程度上影响服务器的使用效率,如果不加以限制,很容易就会把服务器资源耗尽。所以,我们可以通过创建线程池来管理这些线程,提升对线程的使用率。
简而言之,线程池就是管理线程的一个容器,有任务需要处理时,会相继判断核心线程数是否还有空闲、线程池中的任务队列是否已满、是否超过线程池大小,然后调用或创建线程或者排队,线程执行完任务后并不会立即被销毁,而是仍然在线程池中等待下一个任务,如果超过存活时间还没有新的任务就会被销毁,通过这样复用线程从而降低开销。
可能有人就会问了,使用线程池有什么好处吗?那不用说,好处自然是有滴。大概有以下:
1、提升线程池中线程的使用率,减少对象的创建、销毁。
2、线程池的伸缩性对性能有较大的影响,使用线程池可以控制线程数,有效的提升服务器的使用资源,避免由于资源不足而发生宕机等问题。(创建太多线程,将会浪费一定的资源,有些线程未被充分使用;销毁太多线程,将导致之后浪费时间再次创建它们;创建线程太慢,将会导致长时间的等待,性能变差;销毁线程太慢,导致其它线程资源饥饿。)
我们要使用线程池得先了解它是怎么工作的,流程如下图,废话不多说看图就行。核心就是复用线程,降低开销。
1、newCachedThreadPool()(工作队列使用的是 SynchronousQueue)
创建一个线程池,如果线程池中的线程数量过大,它可以有效的回收多余的线程,如果线程数不足,那么它可以创建新的线程。
不足:这种方式虽然可以根据业务场景自动的扩展线程数来处理我们的业务,但是最多需要多少个线程同时处理却是我们无法控制的。
优点:如果当第二个任务开始,第一个任务已经执行结束,那么第二个任务会复用第一个任务创建的线程,并不会重新创建新的线程,提高了线程的复用率。
作用:该方法返回一个可以根据实际情况调整线程池中线程的数量的线程池。即该线程池中的线程数量不确定,是根据实际情况动态调整的。
2、newFixedThreadPool()(工作队列使用的是 LinkedBlockingQueue)
这种方式可以指定线程池中的线程数。如果满了后又来了新任务,此时只能排队等待。
优点:newFixedThreadPool 的线程数是可以进行控制的,因此我们可以通过控制最大线程来使我们的服务器达到最大的使用率,同时又可以保证即使流量突然增大也不会占用服务器过多的资源。
作用:该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁已经创建好的线程,自始自终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数。
3、newScheduledThreadPool()
该线程池支持定时,以及周期性的任务执行,我们可以延迟任务的执行时间,也可以设置一个周期性的时间让任务重复执行。该线程池中有以下两种延迟的方法。
scheduleAtFixedRate 不同的地方是任务的执行时间,如果间隔时间大于任务的执行时间,任务不受执行时间的影响。如果间隔时间小于任务的执行时间,那么任务执行结束之后,会立马执行,至此间隔时间就会被打乱。
scheduleWithFixedDelay 的间隔时间不会受任务执行时间长短的影响。
作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。
4、newSingleThreadExecutor()
这是一个单线程池,至始至终都由一个线程来执行。
作用:该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待这一个线程空闲,当这个线程空闲了再按 FIFO 方式顺序执行任务队列中的任务。
5、newSingleThreadScheduledExecutor()
只有一个线程,用来调度任务在指定时间执行。
作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。只不过和上面的区别是该线程池大小为 1,而上面的可以指定线程池的大小。
使用示例:
//创建一个会根据需要创建新线程的线程池
ExecutorService executor= Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
System.out.println(i);
}
});
}
这五种线程池都是直接或者间接获取的 ThreadPoolExecutor 实例 ,只是实例化时传递的参数不一样。所以如果 Java 提供的线程池满足不了我们的需求,我们可以通过 ThreadPoolExecutor 构造方法创建自定义线程池。
public ThreadPoolExecutor(
int corePoolSize,//线程池核心线程大小
int maximumPoolSize,//线程池最大线程数量
long keepAliveTime,//空闲线程存活时间
TimeUnit unit,//空闲线程存活时间单位,一共有七种静态属性(TimeUnit.DAYS天,TimeUnit.HOURS小时,TimeUnit.MINUTES分钟,TimeUnit.SECONDS秒,TimeUnit.MILLISECONDS毫秒,TimeUnit.MICROSECONDS微妙,TimeUnit.NANOSECONDS纳秒)
BlockingQueue<Runnable> workQueue,//工作队列
ThreadFactory threadFactory,//线程工厂,主要用来创建线程(默认的工厂方法是:Executors.defaultThreadFactory()对线程进行安全检查并命名)
RejectedExecutionHandler handler//拒绝策略(默认是:ThreadPoolExecutor.AbortPolicy不执行并抛出异常)
)
使用示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
jdk 中提供了四种工作队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按 FIFO 排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到 corePoolSize 后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到 maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
基于链表的***阻塞队列(其实最大容量为 Interger.MAX_VALUE),按照 FIFO 排序。由于该队列的近似***性,当线程池中线程数量达到 corePoolSize 后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到 maxPoolSize,因此使用该工作队列时,参数 maxPoolSize 其实是不起作用的。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到 maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具有优先级的***阻塞队列,优先级通过参数 Comparator 实现。
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,就会执行拒绝策略。jdk中提供了4中拒绝策略:
①ThreadPoolExecutor.CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的 run 方法,除非线程池已经 shutdown,则直接抛弃任务。
②ThreadPoolExecutor.AbortPolicy
该策略下,直接丢弃任务,并抛出 RejectedExecutionException 异常。
③ThreadPoolExecutor.DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
④ThreadPoolExecutor.DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。
除此之外,还可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。
本文简单介绍了线程池的一些相关知识,相信大家对线程池的优点,线程池的生命周期,线程池的工作流程及线程池的使用有了一个大概的了解,也希望能对有需要的人提供一点帮助!文中有错误的地方,还请留言给予指正,谢谢~
也欢迎大家关注我的公众号:Java的成神之路,免费领取最新面试资料,技术电子书,架构进阶相关资料等。
标签:开始 进阶 基于 art 传递 属性 repo 执行 shadow
原文地址:https://blog.51cto.com/14956044/2545532