在能自己创建线程的同时为什么要使用线程池?线程池有什么好处?
线程复用,管理线程池,可以控制最大并发数
1.降低资源的消耗
2.提高响应速度
3.方便管理
两种创建线程池的方式
1.是通过Executors工具类(不推荐)
2.是通过ThreadPoolExecutor来创建(推荐)
为什么会推荐ThreadPoolExecutor呢?这两种的区别在哪儿?
我们可以看一看阿里开发手册中的说明,来对比一下以上三种方式的参数
一.通过Executors工具类
ExecutorService pool1 = Executors.newSingleThreadExecutor();//创建单个线程
ExecutorService pool2 = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池
ExecutorService pool3 = Executors.newCachedThreadPool();//可伸缩的线程池
查看这三种方式的源码
1.通过newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
//new LinkedBlockingQueue<Runnable>()这里的是等待队列,也是允许的请求队列
new LinkedBlockingQueue<Runnable>()));
}
2.通过newFixedThreadPool(int nThreads)
//new LinkedBlockingQueue<Runnable>()这里的是等待队列,也是允许的请求队列
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3.通过newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//这里0的后面的一个参数是允许创建的最大线程数
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从以上三种方法可以看到这三种创建线程池的底层实现方法实际上都是通过new ThreadPoolExecutor对象来创建线程池的.
new LinkedBlockingQueue()这里的是等待队列,也是允许的请求队列,我们可以查看源码,这里允许队列中等待的线程数,是最大值
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
所以以上三种创建方式都会造成有OOM的风险,也就是内存溢出异常
二.直接通过ThreadPoolExecutor创建线程池(推荐使用)-----------7大参数
源码,源码中的参数对应上图中的参数说明.
通过ThreadPoolExecutor创建线程池,可以合理的利用资源,控制开销-------通过设置下面的参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
线程池中的四大拒绝策略
上源码:
如果我通创建了一个如下的线程池:然后去调用执行
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, Runtime.getRuntime().availableProcessors(), 3L,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 15; i++) {
pool.execute(new Thread(()->{
System.out.println(Thread.currentThread().getName() + "OK");
}));
}
1.new ThreadPoolExecutor.AbortPolicy() 这个拒绝策略是,如果当等待队列和最大线程数满了但是还有额外的线程等候,会抛出异常
2. new ThreadPoolExecutor.CallerRunsPolicy()这个是让原有调用者去执行这个线程
3.new ThreadPoolExecutor.DiscardOldestPolicy()这个是不会抛出异常,但是会和最先执行的线程进行竞争
4new ThreadPoolExecutor.DiscardPolicy()这个是不会抛出异常
那么问题来了,如何设置最合理的最大线程数来合理的保证资源最高效的利用呢?
这里有两点需要注意
CPU密集型 CPU密集型表示的是电脑或者服务器上硬件的最大线程数,将这个值设为最大线程数可以使资源最大限度的利用化
//这个方法可以获得系统的
int i = Runtime.getRuntime().availableProcessors();
IO密集型,考虑十分占IO的线程任务数量N,一般可以考虑将最大线程数设为2N