JUC-线程池问题
-
要了解线程池,首先先了解一下池化技术
池化技术,提前保存大量的资源,以备不时之需以及重复使用。通俗点理解,就是实现准备好一些资源,有人要用就来这里拿,用完以后要还回来,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。对连接或线程的复用,并对复用的数量、时间等进行控制,从而使得系统的性能和资源消耗达到最优状态。
参考文档
-
接下来了解一下线程池
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
-
那么线程池有哪些好处?
- 降低资源消耗:通过重复利用现有的线程来执行任务,避免多次创建和销毁线程。
- 提高响应速度:因为省去了创建线程这个步骤,所以在拿到任务时,可以立刻开始执行。
- 提供附加功能:线程池的可拓展性使得我们可以自己加入新的功能,比如说定时、延时来执行某些线程。
-
关于线程池的创建,首先看一下JUC下Executors工具类的三大方法
查看API可知,java.util.concurrent.Executors下有三个基本的创建线程池的方法 -
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
import java.util.concurrent.*;
public class Executor01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
try {
for (int i = 0; i < 100; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
- ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
public class Executor02 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
try {
for (int i = 0; i < 20; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
- ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩的,可根据实际线程数进行调整
public class Executor03 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 20; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
这个方法的结果具有不确定性,测试的次数、线程的数量和电脑的性能都可能会对结果产生影响
-
在阿里巴巴开发手册中明确表明,线程池的创建不允许使用Executor,而是使用ThreadPoolExecutor,原文如下
那么,ThreadPoolExecutor是什么呢?我们先来看一波源码
ExecutorService threadPool = Executors.newSingleThreadExecutor(); /** * public static ExecutorService newSingleThreadExecutor() { * return new Executors.FinalizableDelegatedExecutorService * (new ThreadPoolExecutor(1, 1, * 0L, TimeUnit.MILLISECONDS, * new LinkedBlockingQueue<Runnable>())); */ ExecutorService threadPool = Executors.newFixedThreadPool(5); /** * public static ExecutorService newFixedThreadPool(int nThreads) { * return new ThreadPoolExecutor(nThreads, nThreads, * 0L, TimeUnit.MILLISECONDS, * new LinkedBlockingQueue<Runnable>()); */ ExecutorService threadPool = Executors.newCachedThreadPool(); /** * public static ExecutorService newCachedThreadPool() { * return new ThreadPoolExecutor(0, Integer.MAX_VALUE, //21亿 OOM溢出 * 60L, TimeUnit.SECONDS, * new SynchronousQueue<Runnable>()); */
上面三组分别为Exector三大方法的源码片段,从中可以看出,三个方法都调用了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.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方法中包含了7个参数,下面通过几个场景来理解这7个参数的含义
场景一:开启了2个窗口,有三个窗口是关闭的,但是候客区还没有坐满
1、int corePoolSize代表核心线程池大小,即当前场景下开通的窗口数量:2
场景二:窗口1、2满了,候客区也坐满了,但是还有人要办理业务
2、 int maximumPoolSize代表最大核心线程池大小 ,即能够开启的最大的窗口数:5
场景三:当业务都办理完毕,后续再没有人来办理业务的时候
这种场景下,在超过一定时间没有人访问的情况下,后开启的线程会被释放,回到核心线程池大小:5 -> 2
3、long keepAliveTime代表超时了没有人调用就会释放
4、TimeUnit unit代表超时单位
即超过设定的时长就会被释放掉
5、BlockingQueue workQueue代表阻塞队列,此处即候客区
6、ThreadFactory threadFactory代表线程工厂,创建线程的,一般不用动
场景四:当窗口办理人员已满,候客区已满,还有用户要办理业务
7、RejectedExecutionHandler handler //拒绝策略
拒绝策略,即当前场景下,对于再进入办理业务的人员的处理策略
,共有4种拒绝策略
下面用具体的实例来演示一下
- 策略一:new ThreadPoolExecutor.AbortPolicy()
import java.util.concurrent.*;
public class ThreadPoolExecutorTest01{
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2, //核心线程池大小(int corePoolSize)
5, //最大核心线程池大小(int maximumPoolSize)
3, //超时了没有人调用就会释放(long keepAliveTime)
TimeUnit.SECONDS, //超时单位(TimeUnit unit)
new LinkedBlockingQueue<>(3), //阻塞队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.AbortPolicy());//银行满了,还有人进来,不处理这个人的 //抛出异常java.util.concurrent.RejectedExecutionException
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();
}
}
}
- 策略二:哪来的去哪里
import java.util.concurrent.*;
public class ThreadPoolExecutorTest02{
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2, //核心线程池大小(int corePoolSize)
5, //最大核心线程池大小(int maximumPoolSize)
3, //超时了没有人调用就会释放(long keepAliveTime)
TimeUnit.SECONDS, //超时单位(TimeUnit unit)
new LinkedBlockingQueue<>(3), //阻塞队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.CallerRunsPolicy()); //哪来的去哪里
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();
}
}
}
- 策略三:队列满了,丢掉任务,不会抛出异常
import java.util.concurrent.*;
public class ThreadPoolExecutorTest03{
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2, //核心线程池大小(int corePoolSize)
5, //最大核心线程池大小(int maximumPoolSize)
3, //超时了没有人调用就会释放(long keepAliveTime)
TimeUnit.SECONDS, //超时单位(TimeUnit unit)
new LinkedBlockingQueue<>(3), //阻塞队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.DiscardPolicy()); //队列满了,丢掉任务,不会抛出异常
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();
}
}
}
策略四:队列满了,尝试去和最早的竞争,不抛出异常
import java.util.concurrent.*;
public class ThreadPoolExecutorTest04{
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2, //核心线程池大小(int corePoolSize)
5, //最大核心线程池大小(int maximumPoolSize)
3, //超时了没有人调用就会释放(long keepAliveTime)
TimeUnit.SECONDS, //超时单位(TimeUnit unit)
new LinkedBlockingQueue<>(3), //阻塞队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试去和最早的竞争,不抛出异常
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();
}
}
}