并发编程-线程池(五)线程池为什么不允许使用Executors创建
在实际使用线程池的时候,手动创建使用 Excecutor 创建线程会有以下问题。【1】
1、不受控风险大;
无法知道实际生产中,创建和使用的线程数量;
如果每个请求都创建一个线程池,并且通过线程池创建的线程,这样无法控制具体的线程数量;
2、开销大;
如果每个请求都创建一个线程池,并且通过线程池创建的线程,内存就很容易使用完;
同时,会造成线程切换的上下文切换的开销;
创建一个线程的过程
上面已经提到了,创建一个线程还要调用操作系统内核API。为了更好的理解创建并启动一个线程的开销,我们需要看看 JVM 在背后帮我们做了哪些事情:
1)它为一个线程栈分配内存,该栈为每个线程方法调用保存一个栈帧
2)每一栈帧由一个局部变量数组、返回值、操作数堆栈和常量池组成
3)一些支持本机方法的 jvm 也会分配一个本机堆栈
4)每个线程获得一个程序计数器,告诉它当前处理器执行的指令是什么
5)系统创建一个与Java线程对应的本机线程
6)将与线程相关的描述符添加到JVM内部数据结构中
7)线程共享堆和方法区域
一、Executors 线程池创建工具【2】
1、Executors.newCachedThreadPool();
说明:
创建的线程池核心线程0 , 最大线程是Integer.MaxValue。 线程空闲存活时间1分钟。 默认异常拒绝策略,使用SynchronousQueue队
特点:
每次添加任务如果没有空闲线程就会新建一个线程去执行。
SynchronousQueue是阻塞队列,加入任务的线程会阻塞住,直到其它线程从中取走任务才会结束阻塞
线程创建上限近乎无限
适用场景:
所以它适用于任务加入比较稳当且加入间隔短的场景
实现:
new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue());
缺点:
任务队列是SynchronousQueue,线程池对任务来着不拒,线程不够用就创建一个线程。
如果同一时刻应用的来了大量的任务, 这个线程池很容易就创建过多的线程, 容易导致应用卡顿或者直接OOM
2、Executors.newFixedThreadPool(int);
说明:
核心线程和最大线程数是你传入的参数。 其他参数和 Executors.newSingleThreadExecutor一样
实现:
new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
缺点:
这个定死了线程数量, 所以线程数量是不会超出的,但是它的任务队列是无界的LinkedBlockingQueue
加进来的任务处理不过来就会存入任务队列中, 并且无限制的存入队列,很容易导致OOM。
3、Executors.newSingleThreadExecutor();
说明:
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照顺序执行。
特点:
只有一个线程
近乎可以接收无限任务的队列, 可以堆积大量任务
适用于任务持续加入但是任务数并不多的场景
实现:
new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())
缺点:
任务队列和上面一样, 没有限制, 很容易就使用不当导致OOM
4、Executors.newScheduledThreadPool(int);
说明:
创建一个定长线程池,支持定时及周期性任务执行。
特点:
核心线程是传入的参数,最大线程是int上线, 默认存活时间是10毫秒, 任务队列使用自己实现的DelayedWorkQueue, 拒绝策略异常策略
加入任务的时候,会把任务和定时时间构建一个RunnableScheduledFuture对象,再把这个对象放入DelayedWorkQueue队列中,
DelayedWorkQueue是一个有序队列, 他会根据内部的RunnableScheduledFuture的运行时间排序内部对象。
任务加入后就会启动一个线程。 这个线程会从DelayedWorkQueue中获取一个任务。
DelayedWorkQueue内部是按照时间从前完后获取任务的。如果任务的中的时间还没有到。 获取的就是null。 获取任务结束,线程会休眠10毫秒。所以这个定时任务的执行最小间隔是10毫秒的。
内部实现:
new ScheduledThreadPoolExecutor(corePoolSize)
缺点:
这个是定时任务的线程池, 没有定义线程创建数量的上线, 同时任务队列也没有定义上限, 如果前一次定时任务还没有完成, 后一个定时任务的运行时间到了, 它也会运行, 线程不够就创建。
这样如果定时任务运行的时间过长, 就会导致前后两个定时任务同时执行,如果他们之间有锁,还有可能出现死锁。
二、线程池推荐创建方式【3】
1、 方式一:引入 commons-lang3包
ScheduledExecutorService executorService =
new ScheduledThreadPoolExecutor(1,new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
2、方式二:引入:com.google.guava包
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); //Common Thread Pool ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); pool.execute(()-> System.out.println(Thread.currentThread().getName())); pool.shutdown();//gracefully shutdown
3、方式三:spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean
调用execute(Runnable task)方法即可
<bean id="userThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="10" /> <property name="maxPoolSize" value="100" /> <property name="queueCapacity" value="2000" /> <property name="threadFactory" value= threadFactory /> <property name="rejectedExecutionHandler"> <ref local="rejectedExecutionHandler" /> </property> </bean> //in code userThreadPool.execute(thread);
三、ThreadPoolExecutor 使用【4】
https://www.cnblogs.com/dafanjoy/p/9729358.html
---------------------------------------------------------------------------------------------------------------------
来源:
【1】https://zhuanlan.zhihu.com/p/134204957
【3】
【4】https://www.cnblogs.com/dafanjoy/p/9729358.html
【5】https://www.cnblogs.com/dukedu/p/13813443.html