- 通过Executors创建的四种线程池,以及其弊端
Java通过Executors提供四种线程池:
1. newCachedThreadPool() 创建一个可缓存线程池,可灵活回收空闲线程,若无可回收,则新建线程。
2. newFixedThreadPool() 创建一个定长的线程池,超出的线程会在队列中等待.
3. newSingleThreadExecutor() 创建一个单线程的线程池,保证所有的任务FIFO/LIFO.
4. newScheduledThreadPool() 创建一个定长的线程池,支持定时及周期性的任务执行
具体的使用方式如下代码:
package com.lyb269.thread;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
/**
* 四种线程池的使用 ExecutorService
*/
public class ThreadPoolExecutorTest {
@Test
public void testCachedThreadPoll(){
//创建一个可缓存线程池,灵活回收
ExecutorService service = Executors.newCachedThreadPool();
Thread t = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println(current.getName()+" running");
});
for (int i = 0; i < 10; i++){
service.execute(t);
}
//打印如下
//pool-1-thread-1 running
//pool-1-thread-2 running
//pool-1-thread-4 running
//pool-1-thread-3 running
//pool-1-thread-5 running
//pool-1-thread-1 running thread-1回收再用
//pool-1-thread-5 running thread-5回收再用
//pool-1-thread-6 running
//pool-1-thread-7 running
//pool-1-thread-8 running
}
@Test
public void testSingleThreadPool(){
//创建一个单线程的线程池,所有的任务FIFO/LIFO
ExecutorService service = Executors.newSingleThreadExecutor();
Thread t = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println(current.getName()+" running");
});
for (int i = 0; i < 10; i++){
service.execute(t);
}
//打印如下:
//pool-1-thread-1 running
//pool-1-thread-1 running
//pool-1-thread-1 running
//pool-1-thread-1 running
//pool-1-thread-1 running
//pool-1-thread-1 running
//pool-1-thread-1 running
//pool-1-thread-1 running
//pool-1-thread-1 running
//pool-1-thread-1 running
//始终只有 thread-1在执行
}
@Test
public void testFixedThreadPool(){
//创建一个定长的线程池
ExecutorService service = Executors.newFixedThreadPool(2);
Thread t = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println(current.getName()+" running");
});
for (int i = 0; i < 10; i++){
service.execute(t);
}
//打印如下:
//pool-1-thread-2 running
//pool-1-thread-1 running
//pool-1-thread-2 running
//pool-1-thread-1 running
//pool-1-thread-2 running
//pool-1-thread-1 running
//pool-1-thread-2 running
//pool-1-thread-1 running
//pool-1-thread-2 running
//pool-1-thread-1 running
//始终只有thread-1,thread-2两个线程在跑,谁先释放谁来
}
@Test
public void testScheduledThreadPool(){
//创建一个定长的线程池,支持定时及周期任务
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Thread t = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println(current.getName()+" running;执行时间:"+sdf.format(new Date()));
});
//延迟2秒执行
System.out.println("当前时间:"+sdf.format(new Date()));
service.schedule(t,2, TimeUnit.SECONDS);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//马上执行(延迟0秒)后,每隔两秒执行一次
System.out.println("当前时间:"+sdf.format(new Date()));
service.scheduleAtFixedRate(t, 0L, 2L, TimeUnit.SECONDS);
//打印如下:
//当前时间:2020-03-03 21:09:21
//pool-1-thread-1 running;执行时间:2020-03-03 21:09:23
//当前时间:2020-03-03 21:09:24
//pool-1-thread-1 running;执行时间:2020-03-03 21:09:24
//pool-1-thread-1 running;执行时间:2020-03-03 21:09:26
//pool-1-thread-1 running;执行时间:2020-03-03 21:09:28
// .....隔两秒执行一次,一直打印下去....
}
}
以上四种创建线程池,都不推荐使用.
1.四种方式本质上都是通过ThreadPoolExecutor来创建
int corePoolSize, 线程池维护的最少数量
int maximumPoolSize, 线程池维护的最大数量
long keepAliveTime, 线程池维护线程所允许的空闲时间
TimeUnit unit, 所允许空间时间的单位
BlockingQueue<Runnable> workQueue, 线程池所使用的缓冲队列
ThreadFactory threadFactory, 线程工厂
RejectedExecutionHandler handler 拒绝任务的策略
2.创建可缓存的线程newCachedThreadPool()
newCachedThreadPool设置的最大线程允许数量,是Integer.MAX_VALUE,这样子会造成OOM,内存溢出.
3.创建支持周期性的线程池 newScheduledThreadPool()
newScheduledThreadPool()允许的最大线程数,也是Integer.MAX_VALUE,也会创建大量的线程,造成OOM,内存溢出.
4.创建定长的线程池 newFixedThreadPool()
newFixedThreadPool() 使用的线程任务缓存队列是LinkedBlockingQueue,LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE. 有可能造成大量的请求堆积,从而OOM内存溢出.
5.创建单线程的线程池,newSingleThreadPool()
同newFixedThreadPool() 一样,使用LinkedBlockingQueue缓存任务队列,造成大量请求堆积而OOM内存溢出.
- 通过ThreadPoolExecutor创建
避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数自己创建线程池。在创建的同时:
1.给任务缓存队列 BlockingQueue指定容量
2.评估业务需求,创建合适的最大线程池允许数
@Test
public void createThreadPoolTest(){
//创建一个最小数量为1 最大为5,维护线程所允许的时间为30s,缓存队列为5,采用默认的拒绝策略以及ThreadFactory
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 5,
30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
Runnable r = () -> {
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() +" ---> running");
};
for (int i = 0; i < 200; i++){
executor.execute(r);
}
//打印如下:
//pool-1-thread-1 ---> running
//pool-1-thread-1 ---> running
//pool-1-thread-1 ---> running
//pool-1-thread-1 ---> running
//pool-1-thread-1 ---> running
//pool-1-thread-1 ---> running
//pool-1-thread-2 ---> running
//pool-1-thread-3 ---> running
//pool-1-thread-4 ---> running
//pool-1-thread-5 ---> running
//然后抛出异常 java.util.concurrent.RejectedExecutionException
//这是因为任务缓存超过ArrayBlockingQueue设置得大小,然后我们创建得时候采用默认得拒绝策略AbortPolicy(抛出异常)
}
ThreadPoolExecutor的拒绝策略有四种:
ThreadPoolExecutor.AbortPolicy(): 抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardOldestPolicy(): 抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy(): 抛弃当前的任务
ThreadPoolExecutor.CallerRunsPolicy(): 重试添加当前的任务,自动重复调用execute()方法