线程池
1.Java常见的四种线程池
线程池原理:ThreadPoolExecutor
里面使用到JUC同步器框架AbstractQueuedSynchronizer
(俗称AQS
)、大量的位操作、CAS
操作。ThreadPoolExecutor
提供了固定活跃线程(核心线程)、额外的线程(线程池容量 - 核心线程数这部分额外创建的线程,下面称为非核心线程)、任务队列以及拒绝策略这几个重要的功能。
1.1 newFixedThreadPool(int poolSize)
创建一个大小为poolSize的固定线程池,线程池是无边界阻塞队列。代码如下
ExecutorService exec = Executors.newFixedThreadPool(poolSize);
//它实际上是创建了一个ThreadPoolExecutor实例
new
ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new
LinkedBlockingQueue<Runnable>());
//第一个参数是核心线程数;第二个参数是最大线程数;0L是长整型,代表当线程数量大于核心数时,在终止前,额外线程等待新任务的最大时间;第四个参数代表第三个参数的时间单位,这里是毫秒,第五个参数代表创建一个无边界的队列。
1.2 newSingleThreadExecutor()
创建一个单线程池,类似于newFixedThreadPool(1)ExecutorService exec = Executors.newSingleThreadExecutor();
//它创建了一个被FinalizableDelegatedExecutorService修饰的ThreadPoolExecutor实例,所以它只有ThreadPoolExecutor的部分功能,且会被GC回收
new
FinalizableDelegatedExecutorService(
new
ThreadPoolExecutor(
1
,
1
, 0L, TimeUnit.MILLISECONDS,
new
LinkedBlockingQueue<Runnable>()));
1.3 newCachedThreadPool()
创建一个具有缓冲功能的线程池
ExecutorService exec = Executors.newCachedThreadPool();
//它创建了ThreadPoolExecutor实例,它是一个同步队列
new
ThreadPoolExecutor(
0
, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new
SynchronousQueue<Runnable>());
//这会创建一个无界的线程池,线程可以空闲的最大时间是60秒,在这60秒都是已创建的线程都是可以复用的。
1.4 newScheduledThreadPool(int poolSize)
创建一个具有计划执行的固定大小线程池
ExecutorService exec = Executors.newScheduledThreadPool(poolSize)
//它创建了ThreadPoolExecutor实例,但是使用的是优先级队列
new
ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE,
0
, NANOSECONDS,
new
DelayedWorkQueue())
2.ThreadPoolExecutor
由于使用上面的线程池或多或少有些限制,所以使用底层的ThreadPoolExecutor可以自定义线程池的属性。
2.1 参数详解
第一个参数corePoolSize意思是核心线程大小;线程池中保留的线程数,即使是空闲状态也保留,除非设置了允许核心线程超时,默认是不设置,即不销毁核心线程
第二个参数是最大线程数;
第三个是keepAliveTime保留时间,当最大线程数大于核心线程数时,多余的线程保留时间,超过这个时间多余的线程将被销毁。
第四个是第三个时间的时间单位,是属于TimeUnit的枚举值。
第五个是一个阻塞队列,在任务提交执行前,任务都是这个阻塞队列的内容。
第六个是线程工厂,这种工厂模式会被用于创建新线程。
第七个是拒绝策略,当线程池线程达到最大值,阻塞队列排满时使用这个参数,,默认是抛弃执行超出的任务,直接抛异常。
2.2 阻塞队列
2.2.1 ArrayBlockingQueue
基于数组的有界阻塞队列,规则是FIFO(先进先出)。
2.2.2 LinkedBlockingQUeue
基于链表的无界阻塞队列,规则也是FIFO,如果不设定大小,默认是无界的,此时可能会造成内存溢出等问题。
2.2.3 SychronousQueue
同步的阻塞队列,它不存储元素,上一个元素执行完才能执行下一个元素。
2.3 拒绝策略
ThreadPoolExecutor类有4个拒绝策略的内部类。默认的拒绝策略是AbortPolicy。
2.3.1 AbortPolicy
线程池达到最大线程数且阻塞队列满,直接抛出异常
2.3.2 DiscardPolicy
线程池达到最大线程数且阻塞队列满,遗弃这个任务
2.3.3 DiscardOldestPolicy
线程池达到最大线程数且阻塞队列满,遗弃最旧的任务,尝试将新任务添加到新线程池
2.3.4 CallerRunPolicy
线程池达到最大线程数且阻塞队列满,尝试使用主线程执行该任务
2.4 线程池工作原理
2.4.1 原理
第一步,线程池判断核心线程池是否都在执行任务,若不是,则创建线程来执行任务;若是,则执行第二步
第二步,使用阻塞队列来存储任务,若阻塞队列已满,则执行第三步。
第三步,线程池判断现在是否达到最大线程数,若不是,创建线程来执行任务,若是,则执行饱和策略。
参数如何设置:
对于 I/O 操作或其他 阻塞任务,由于线程并不会一直执行,因此线程池的规模应该更大 2N大小
线程池基本原理:
实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行
//代码 submit 和execute区别 submit 可以返回值 也可以用get 等待当前所有子线程完成。
package cn.shenzhen.nk.Thread.pool;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class InvokeAll {
private static Logger log = LogManager.getLogger(InvokeAll.class);
// public static int addNum = 0;
public static void main(String[] args) throws ExecutionException, InterruptedException {
test();
}
public static void test() throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
List<Future> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Future future = threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
futures.add(future);
}
for (Future future : futures
) {
future.get();
}
System.out.println("主线程已经执行完了");
threadPoolExecutor.shutdown();
try {
if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
threadPoolExecutor.shutdownNow();
if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
System.out.println("线程池没有正常关闭");
}
}
} catch (InterruptedException e) {
threadPoolExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
//当线程池shotdown时候,线程池不再接收任何新任务,但此时线程池并不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出,调用shutdown方法后我们可以在一个死循环里面用isTerminated方法判断是否线程池中的所有线程已经执行完毕,如果子线程都结束了,我们就可以做关闭流等后续操作了
//这种无限循环我觉得还是别用
while (true) {
if (threadPoolExecutor.isTerminated()) {
System.out.println("所有的子线程都结束了!");
Long end = System.currentTimeMillis();
System.out.println(end - start);
break;
}
}
}
}