线程池需要掌握的知识点:
三大方法、七大参数、四种拒绝策略
讲线程池前,我们得先了解一下池化技术
池化技术通俗讲就是我事先准备好一些资源,有人需要用就可以来我这里借去使用,用完后再还回来就可以了,这样还回来的资源又可以让后面需要来借的人拿去使用。
线程池就是利用的池化技术,把多个线程维护在一个池子里,池子里面的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
这样做的好处是:1.可以降低资源的消耗;2. 提高响应的速度;3.提高线程的可管理性。
(总结成一句话:线程复用、可控制最大并发数、管理线程)
Executors工具类的三大方法
Executors.newSingleThreadExecutor();用于创建单个线程
Executors.newFixedThreadPool(7);创建的线程池的大小为括号里的值
Executors.newCachedThreadPool();创建一个可伸缩的线程池,即线程池大小根据实际情况调整,遇强则强,遇弱则弱。
public class PoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService threadPool = Executors.newFixedThreadPool(7);
// ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i <10 ; i++) {
//使用线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//用完线程池,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
上面讲到的三种方法是利用Executors去创建,但在实际开发中我们不允许这种方法去创建线程池,因为这样的方式可能会创建大量的线程,从而导致OOM,所以在实际开发中是通过ThreadPoolExecutor的方式来创建线程池,这样可以规避资源耗尽的风险。
ThreadPoolExecutor的七大参数
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
怎么通过ThreadPoolExecutor来创建一个自定义线程?我们来通过一个银行办理业务的例子来讲解。
如下图,银行有5个办理业务的窗口,这里的数量5就是本例子中最大核心线程池大小,平时只开1,2两个窗口,这里的1,2就是本例子中的核心线程池,且候客区有三个位置。
假设有一天,去银行办理业务的人多了起来,这个时候银行为了更高效处理业务,把剩下的窗口也全部开放,但是人还是很多,此时候客区也已经坐满(这里的候客区就是本例子中的阻塞队列),这个时候如果还有人要进来银行办理业务,那么我们就需要调用到我们的拒绝策略来进行处理。
四种拒绝策略
new ThreadPoolExecutor.AbortPolicy() //银行满了,若还有人进来,则抛出异常
new ThreadPoolExecutor.CallerRunsPolicy()//银行满了,若还有人进来,叫他回去
new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试去和最早的竞争,不抛出异常
new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不抛出异常
银行办理业务例子代码实现
public class PoolDemo {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool=new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
//银行满了,还有人进来,不处理这个人,抛出异常
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 1; i <=5 ; i++) {
//使用线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//用完线程池,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
声明:本笔记是学习狂神juc课程所做的笔记,图片和部分代码来自课堂笔记,大家可以点击链接观看并三连予以支持。
https://www.bilibili.com/video/BV1B7411L7tE?p=21