参考博客:https://blog.youkuaiyun.com/hnd978142833/article/details/80253784
一、线程池简介
- 线程池的概念:
线程池就是提前创建好线程,将创建好的线程集中在一起就是线程池。线程池可以很好的提高系统的性能,线程池在系统启动时,会创建核心空闲线程,程序将一个任务提交给线程池,线程池就会启动一个线程来执行任务,执行完任务后,不会销毁,而是返还线程池中,重新置为空闲状态,等待下一次的任务执行。 - 线程池的工作机制:
2.1.在线程池的编程模式下,任务是提交给线程池,而不是提交给某个线程,线程池在拿到任务后,如果有空闲线程,就在其内部选择一个空闲的线程来执行该任务。
2.2.一个线程同时只能执行一个任务,但是可以向线程池提交多个任务。 - 使用线程池的原因:
多线程环境下,系统会不断的创建和销毁线程,以及频繁的线程上下文切换,造成资源的浪费,系统性能下降。故线程池是一个好的选择。
二、四种常见线程池详解:
- newSingleThreadExecutor():创建一个只有一个线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务的按照指定(FIFO)顺序执行。
内部实现:
//可见下方线程池参数,再回来观看。
public static ExecutorService newSingleThreadExecutor() {
//初始创建一个核心线程只有1个,最大线程只有1个的线程池。
//new LinkedBlockingQueue() 阻塞队列的大小为:Integer.MAX_VALUE
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
测试:
public static void main(String[] args) {
//创建线程池
ExecutorService executorService= Executors.newSingleThreadExecutor();
//将10个任务提交给线程池
for (int i = 0; i < 10; i++) {
final int tmp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->" + tmp);
}
});
}
executorService.shutdown();
}
输出结果:
pool-1-thread-1—>0
pool-1-thread-1—>1
pool-1-thread-1—>2
pool-1-thread-1—>3
pool-1-thread-1—>4
pool-1-thread-1—>5
pool-1-thread-1—>6
pool-1-thread-1—>7
pool-1-thread-1—>8
pool-1-thread-1—>9
- newCachedThreadPool():创建一个可缓存的线程池,先查看线程池是否有可用的空闲线程,有则复用该线程,没有则创建一个新的线程加入线程池中,适用于执行许多短期的异步任务。
内部实现:
public static ExecutorService newCachedThreadPool() {
//初始创建一个线程池,里面没有核心线程,最大线程数量为Integer.MAX_VALUE,相当于无穷大,
//空闲线程超过60s,就会被销毁。
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()); //同步队列
}
测试:
public static void main(String[] args) {
//创建线程池
ExecutorService executorService= Executors.newCachedThreadPool();
//将10个任务提交给线程池
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
final int tmp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->" + tmp);
}
});
}
executorService.shutdown();
}
输出结果:
pool-1-thread-1—>0
pool-1-thread-2—>1
pool-1-thread-2—>2
pool-1-thread-2—>3
pool-1-thread-2—>4
pool-1-thread-1—>6
pool-1-thread-1—>7
pool-1-thread-2—>5
pool-1-thread-2—>8
pool-1-thread-2—>9
- newFixedThreadPool():创建一个可重用的固定数量的核心线程和最大线程的线程池,且线程池的线程不会被销毁,直到线程池shutdown。
内部实现:
public static ExecutorService newFixedThreadPool(int nThreads) {
//指定线程池的核心线程和最大线程数量为 nThreads,且线程创建的线程不会被销毁。
//new LinkedBlockingQueue() 阻塞队列的大小为:Integer.MAX_VALUE
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
测试:
public static void main(String[] args) {
//创建线程池
ExecutorService executorService= Executors.newFixedThreadPool(5);
//将10个任务提交给线程池
for (int i = 0; i < 10; i++) {
final int tmp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->" + tmp);
}
});
}
executorService.shutdown();
}
输出结果:
pool-1-thread-1—>0
pool-1-thread-1—>5
pool-1-thread-3—>2
pool-1-thread-2—>1
pool-1-thread-3—>7
pool-1-thread-4—>3
pool-1-thread-1—>6
pool-1-thread-5—>4
pool-1-thread-4—>9
pool-1-thread-2—>8
- newScheduledThreadPool():创建一个定长线程池,支持定时和周期性的执行任务。
内部实现:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//创建一个核心线程数为corePoolSize,最大线程数为Integer.MAX_VALUE的线程池
//创建的线程不会被销毁。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue()); //专门的延迟队列
}
测试:
public static void main(String[] args) {
//创建线程池
ExecutorService executorService = Executors.newScheduledThreadPool(5);
//将10个任务提交给线程池
for (int i = 0; i < 10; i++) {
final int tmp = i;
((ScheduledExecutorService) executorService).schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->" + tmp);
}
},1,TimeUnit.SECONDS); //延时1s,再执行
}
executorService.shutdown();
}
输出结果:
//此处会等待1s后再输出
pool-1-thread-1—>0
pool-1-thread-5—>1
pool-1-thread-3—>3
pool-1-thread-4—>2
pool-1-thread-2—>4
pool-1-thread-1—>5
pool-1-thread-3—>8
pool-1-thread-4—>9
pool-1-thread-2—>7
pool-1-thread-5—>6
三、上面四种创建线程池的方式,其底层都是同一个对象,那就是:
new ThreadPoolExecutor(int corePoolSize, //线程池的核心线程数量
int maximumPoolSize, //线程池的最大线程数量
long keepAliveTime, //线程保持存活的时间
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂,创建线程的
RejectedExecutionHandler handler); //拒绝策略
1.什么时候触发最大线程数量?
当核心线程都被使用了,而且阻塞队列也满了,这时如果还有任务要处理,就会触发最大线程数量。
2.保持存活时间,是指那些不是核心线程的线程,他们被创建出来使用完后,处于空闲状态,当达到这个存活时间时,这些线程就会被销毁。
3.当我们处理任务达到最大线程池数量,且阻塞队列也满了,还有任务要处理,这些任务就会被拒绝策略处理。
四、拒绝策略:
RejectedExecutionHandler的实现类:
AbortPolicy: 拒绝任务,抛出一个RejectedExecutionException异常。
CallerRunsPolicy: 哪来的回到哪里去 执行。由调用线程来处理该任务。
DiscardPolicy: 不会抛出异常,默认直接丢弃任务。
DiscardOldestPolicy:丢弃最旧的任务,然后重新提交被拒绝的任务。
下面自定义线程池来测试拒绝策略:
public static void main(String[] args) {
//创建线程池,自定义线程池参数
ExecutorService executor = new ThreadPoolExecutor(
2, //核心线程数量
5, //最大线程数量
3, //最大存活时间
TimeUnit.SECONDS, //单位s
new LinkedBlockingQueue<>(3), //阻塞队列的大小为3
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
//测试
/**
* i=2: 阻塞队列为空,由两个核心线程处理
* i=5: 阻塞队列为满,由两个核心线程处理
* i=6: 阻塞队列为满,会加多一个线程和两个核心线程一起处理
* i=7:阻塞队列为满,会加多两个线程和两个核心线程一起处理
* i=8:阻塞队列为满,会加多三个线程和两个核心线程一起处理,此时线程池的线程数量达到线程池的最大上限。
* i=9:线程的最大线程上限 + 阻塞队列的大小 < 待处理的任务,多余的任务会被拒绝策略拒绝:
* new ThreadPoolExecutor.AbortPolicy():使用这个拒绝策略,会直接抛java.util.concurrent.RejectedExecutionException异常
* new ThreadPoolExecutor.CallerRunsPolicy():使用这个拒绝策略,会将任务交由调用者来处理,(这里是main线程来处理)。
* new ThreadPoolExecutor.DiscardPolicy():使用这个拒绝策略,会将任务直接丢弃,且不会抛出异常。
* new ThreadPoolExecutor.DiscardOldestPolicy():使用这个拒绝策略,会将前面的旧任务丢弃,然后重新提交被拒绝的任务。
*/
for (int i = 0; i < 9; i++) { //模拟i个任务
final int tmp = i;
executor.execute(()->{
System.out.println(Thread.currentThread().getName()+"=>"+tmp);
});
}
executor.shutdown(); //关闭线程池
}
五、阻塞队列:
https://www.jianshu.com/p/4df275facc15
-
缓冲队列BlockingQueue简介:
BlockingQueue是双缓冲队列。BlockingQueue内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。 -
常用的几种BlockingQueue:
-
ArrayBlockingQueue(int i):规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。
-
LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。
-
PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。
-
SynchronizedQueue():特殊的BlockingQueue,对其的操作必须是放和取交替完成。