java全局线程池_JAVA线程池的创建与使⽤
为什么要⽤线程池?
我们都知道,每⼀次创建⼀个线程,JVM后⾯的⼯作包括:为线程建⽴虚拟机栈、本地⽅法栈、程序计数器的内存空间(下图可看出),所以线程过多容易导致内存空间溢出。同时,当频繁的创建和销毁线程容易浪费系统的计算能⼒在资源的回收和申请中。
另外:创建过多的线程,会导致cpu在线程中的切换时间⽐处理时间还多,⼤⼤降低了系统的吞吐量。因此我们使⽤线程池如下好处:
有效控制线程的数量,防⽌线程数量过多。
提⾼线程的利⽤程度,避免频繁的创建及销毁线程。
有更灵活的线程使⽤⽅式及拒绝措施。
java线程池创建的四种再给⼤家看看阿⾥开发规约⾥⾯是怎么说的
线程的快速⽰例
我知道⼤多数⼈都希望先看看线程池怎么创建,然后再深⼊了解。下⾯给⼤家⼀个demo
1 //存放任务的阻塞队列
2 BlockingDeque queue = new LinkedBlockingDeque<>(10);
3 //BasicThreadFactory是⾃⼰实现ThreadFactory接⼝⽽来
4 BasicThreadFactory factory = newBasicThreadFactory();
5 ThreadPoolExecutor poolExecutor = new
ThreadPoolExecutor(3, 10, 60,6 TimeUnit.SECONDS, queue, factory,7 (Runnable r, ThreadPoolExecutor executor)->{8 System.out.Queue().size()+"消息队列已满");9 System.out.println("拒绝服务");10
11 });
ThreadPoolDemo
线程池相关概念
核⼼线程:若线程池中的线程标记为核⼼线程,即使核⼼线程没有运⾏任务,它也不会被销毁,会⼀直存在于线程池中,直⾄线程池被shutdown。
⾮核⼼线程:当线程池中没有空闲的核⼼线程时,线程池会创建⼀个⾮核⼼线程,并且⾮核⼼线程的⼀定时间内处于空闲状态的时候,⾮核⼼线程会被销毁。
阻塞队列:阻塞队列是当线程池中的没有能⽤于处理任务的线程时,会把该任务放⼊阻塞队列,待有能⽤于处理的线程时,把任务从队列取出处理,阻塞队列的长度可以设置。
拒绝服务处理:当线程池中的没有线程能提供处理,并且阻塞队列的空间已满,此时会触发拒绝服务异常,开发⼈员可以根据⾃⼰的需求定制不同的处理策略。
创建线程池的7个参数
⼀般我们推荐使⽤ThreadPoolExecutor()⾃定义创建线程池,因为这⽐较灵活切可控。
int corePoolSize 核⼼线程数,即确定有多少个核⼼线程。
int maximumPoolSize 最⼤线程数,即限定线程池中的最⼤线程数量。
long keepAliveTime ⾮核⼼线程的存活时间,配合下⾯的TimeUnit参数确定时间。
TimeUnit unit ⼀个时间类型的枚举类。有从纳秒到天的时间量度,配合上⾯的keepAliveTime确定⾮核⼼线程的存活时间。
BlockingQueue workQueue 装载Runnable的阻塞队列,具体类型可以⾃⼰确定。
ThreadFactory threadFactory 线程⼯⼚,这是⼀个函数式接⼝,⾥⾯只定义了⼀个newThread(Runnable task)⽅法,需要⾃⼰实现⼯⼚的⽅法,在这⾥我们可以对线程进⾏⾃定义的初始化,例如给线程设定名字,这样⽅便后期的调试。
RejectedExecutionHandler handler 拒绝服务处理,这也是⼀个函数式接⼝,我们需要实现rejectedExecution(Runnable r, ThreadPoolExecutor executor)这个⽅法,这⾥可以根据需求⾃定义你希望在处理逻辑。当然Java⾥⾯也有已经定义好的四种策略静态类。可以通过ThreadPoolExecutor调⽤
Executors中实现的线程池类型
下⾯介绍的线程池类型,是Jdk帮我们制定好的策略。但是,有的线程池类型中,要么存在线程数量⽆限制、要么存在阻塞队列长度⽆限制,但是这些应该在开发中避免,因为⼀旦并发过⾼,会导致⼤量
的对象积压,导致JVM内存溢出。
写在前⾯:jdk提供了默认的⼯⼚⽅法和默认的默认的拒绝处理策略。
默认拒绝策略是:不执⾏并抛出异常
默认的⼯⼚⽅法是:对线程进⾏安全检查并命名。
1 static class DefaultThreadFactory implementsThreadFactory {
2 private static final AtomicInteger poolNumber = new AtomicInteger(1);
3 private finalThreadGroup group;
4 private final AtomicInteger threadNumber = new AtomicInteger(1);
5 private finalString namePrefix;6
7 DefaultThreadFactory() {8 SecurityManager s =SecurityManager();9 group = (s != null) ?s.getThreadGroup()
:10 Thread.currentThread().getThreadGroup();11 namePrefix = "pool-" +
AndIncrement() +
13 "-thread-";14 }15
16 publicThread newThread(Runnable r) {17 Thread t = newThread(group, r,18 namePrefix
+AndIncrement(),19 0);20 if(t.isDaemon())21 t.setDaemon(false);22 if (t.getPriority()
!=Thread.NORM_PRIORITY)23 t.setPriority(Thread.NORM_PRIORITY);24 returnt;25 }26 }
defaultFactory
FixedThreadPool 固定核⼼线程的线程池。
特点:它的核⼼线程数量就是最⼤线程数,所以线程池内的线程永远不会消亡,它采⽤了⽆参数的链表阻塞队列,最⼤的任务数可达232-1个。因此存在任务积压导致内存溢出的风险。
2. CachedThreadPool 缓存线程池
特点:没有核⼼线程,线程池不能满⾜任务运⾏时会创建新的线程,线程数量没有上限。默认的消亡时间为60秒。值得注意的是:它的阻塞队列是SynchronousQueue,这是⼀个没有存储性质的阻塞队列,它的取值操作和放⼊操作必须是互斥的。根据源码⽂档的解释,可以理解为每当有任务放⼊时会⽴即有线程将它取出执⾏。
3. ScheduledThreadPool 固定调度线程池
特点:有固定的核⼼线程,线程的数量没有限制,默认存活时间为60秒。同时⽀持定时及周期性任务执⾏。
4. SingleThreadExecutor 单核⼼线程池
特点:只有⼀个核⼼线程,所以能保证任务的串⾏化执⾏。
5. WorkStealingPool 并⾏执⾏线程池
特点:在jdk8中实现 线程池。它内部的线程池实现是ForkJoinPool,这是⼀个可以同时利⽤多个线程来执⾏任务的线程池。⽆参默认使⽤CPU数量的线程数执⾏任务,由于这个线程池⽐较复杂,下次专门写⼀篇博⽂⽤于更新。
线程池的调⽤流程
需要注意的是:线程池设计的流程是先利⽤核⼼线程处理、核⼼线程不能处理即把它放⼊阻塞队列,最好才创建线程来执⾏任务,直到新建线程也失败才调⽤拒绝服务处理。
试着理解⼀下这样设计的好处。可以看到,创建线程永远不是最先想到的办法,线程池尽量避免创建线程。因为创建线程需要调⽤全局锁来确定线程的正确创建,同时也因为线程创建和销毁也需要消耗资源,所以这种⽅式在最⼤努⼒的避免这种情况的发⽣。
线程池的关闭
虽然在实际的开发中,线程池⼀般是随着项⽬的部署⼀起存活的,不会经常关闭,但是还是需要了解如何关闭,怎么关闭⽐较安全。
线程池可通过调⽤线程池的shutdown或shutdownNow⽅法来关闭线程池.
它们的原理是遍历线程池中的⼯作线程,然后逐个调⽤线程的interrupt⽅法来中断线程,所以⽆法响应中断的任务可能永远⽆法终⽌.
但是它们存在⼀定的区别
shutdownNow⾸先将线程池的状态设置成STOP,然后尝试停⽌所有的正在执⾏或暂停任务的线程,并返回等待执⾏任务的列表
shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执⾏任务的线程.
只要调⽤了这两个关闭⽅法中的任意⼀个,isShutdown⽅法就会返回true.
当所有的任务都已关闭后,才表⽰线程池关闭成功,这时调⽤isTerminaed⽅法会返回true.
⾄于应该调⽤哪⼀种⽅法,应该由提交到线程池的任务的特性决定,通常调⽤shutdown⽅法来关闭线程池,若任务不⼀定要执⾏完,则可以调⽤shutdownNow⽅法.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论