JUC全文
线程池
- ThreadPool
- 一种线程使用模式,线程过多会带来调度开销;线程池维护多个线程,等待监督管理者分配可并发执行的任务,保证内核充分利用,防止过度调度
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
- 架构
submit和excute
submit可以传入Runnable和Callable接口,如果传入Callable,通过返回的future调用get获取返回值;可以通过get获取异常excute没有返回值;只能在run方法用try..catch处理异常
分类
- 一池N线程
- 线程池中的线程处于一定的量,可以很好地控制线程的并发量
- 线程可以重复被使用,在显式关闭之前,都将一直存在
- 超出一定量的线程,任务被提交时候需要在队列中等待
//一池N线程
ExecutorService threadPoolN = Executors.newFixedThreadPool(5);
try {
for (int i = 0; i < 10; i++) {
threadPoolN.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池关闭
threadPoolN.shutdown();
}
- 一池一线程
//一池一线程
ExecutorService threadPoolS = Executors.newSingleThreadExecutor();
- 线程池线程数量不固定,可扩容
//可扩容线程
ExecutorService threadPoolC = Executors.newCachedThreadPool();
参数
public ThreadPoolExecutor(
int corePoolSize, //常驻线程数量,核心线程
int maximumPoolSize, //最大的线程数量,最大支持的
long keepAliveTime, //线程存活时间,非核心线程空闲时的存活时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列,阻塞不能进行执行的任务
ThreadFactory threadFactory, //线程工厂,创建线程
RejectedExecutionHandler handler) //拒绝策略,无法生成新的线程执行方法的反应
newCachedThreadPool的队列为SynchronousQueue,只有一个节点,一次队列中只能用有一个任务进行排队,内部没有容器,一个生产线程,当它生产产品(即put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞newSingleThreadExecutor和newFixedThreadPool队列为LinkedBlockingQueue,长度为Integer.MAX_VALUE
底层原理
- 执行
execute之后才会创建线程 - 当有任务时,且核心线程空闲,直接调用核心线程
- 当有任务时,核心线程没有空闲的,先在阻塞队列中进行等待
- 当阻塞队列满时,有任务时,创建新线程处理任务,不取阻塞队列中的,会插队
- 当核心线程、阻塞队列、最大线程数满时,对任务进行拒绝
执行优先级和提交优先级
- 提交优先级,核心、队列、非核心
- 执行优先级,核心、非核心、队列
execute方法,提交任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//ctl进行原子操作的线程数目
int c = ctl.get();
//线程数 < 核心线程数,添加到核心线程 addWoker第二个参数为`boolean core`为true
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//优先放入队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//最后用非核心线程,插队调用
else if (!addWorker(command, false))
//如果失败,采用拒绝策略
reject(command);
}
拒绝策略
- AbortPoolicy(默认),抛出
RejectedExecutionException阻止系统正常运行,并执行完队列中的任务 - CallerRunsPolicy,调用者模式,不会抛弃任务,也不会抛出异常,将某些任务退回到调用者,降低新任务的流量
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) { //线程池关闭了,就什么都不做
r.run(); //否则就让调用者执行,主线程
}
}
- DiscardOldestPolicy,抛弃队列中等待最久的任务,把当前任务加入到队列中
- DiscardPolicy,默默丢弃无法处理的任务,不予处理也不抛出异常,允许任务丢失
自定义线程
- 实际开发中需要自定义线程池,避免自带的线程池,其中
newSingleThreadExecutor和newFixedThreadPool会出现内存溢出(任务队列将会爆满),newCachedThreadPool会导致CPU百分百,生成大量线程(有一个任务,就创建一个线程) - 可能造成堆积大量的请求,造成
OOM异常
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池关闭
threadPool.shutdown();
}
线程池状态
- Running,能接受新任务以及处理已添加的任务;
- Shutdown,不接受新任务,可以处理已经添加的任务,也就是不能再调用execute或者submit了;
- Stop,不接受新任务,不处理已经添加的任务,并且中断正在处理的任务;
- Tidying,所有的任务已经终止,CTL记录的任务数量为0,CTL负责记录线程池的运行状态与活动线程数量;
- Terminated,线程池彻底终止,则线程池转变为terminated的状态。
分支合并框架
- 递归实现
- Fork/Join,讲一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的结果
fork,将一个复杂的任务进行分拆 join,把分拆任务的结果合并
- ForkJoinPool,分支合并池
案例
- 1加到100
class MyTask extends RecursiveTask<Integer> {
//拆分差值10
private static final Integer DIFF = 10;
private int begin; //开始
private int end; //结束
private int res; //结果
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
//判断差是否大于10
if (end - begin <= DIFF) {
for (int i = begin; i <= end; i++) {
res += i;
}
} else {
//拆分
int mid = begin + end >> 1;
//拆左边
MyTask leftTask = new MyTask(begin, mid);
//拆右边
MyTask rightTask = new MyTask(mid + 1, end);
leftTask.fork();
rightTask.fork();
//合并结果
res = leftTask.join() + rightTask.join();
}
return res;
}
}
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyTask task = new MyTask(1, 100);
//创建分支合并池
ForkJoinPool fjp = new ForkJoinPool();
ForkJoinTask<Integer> fkt = fjp.submit(task);
//获取结果
System.out.println(fkt.get());
//关闭池对象
fjp.shutdown();
}
}
异步回调
- 同步方法,调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
- 异步方法,调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而,异步方法通常会在另外一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工作,执行完成后,通知调用者
- CompletableFuture
//无返回值
CompletableFuture<Void> cfv = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "-cfv");
});
cfv.get();
//有返回值
CompletableFuture<Integer> cfi = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "-cfi");
int i = 1/0;
return 1024;
});
//主线程阻塞
cfi.whenComplete((t, u) -> {
System.out.println("t - " + t); //返回值
System.out.println("u - " + u); //异常信息
}).get();
Future的缺点
- 不支持手动完成
- 不支持进一步的非阻塞调用
- 不支持链式调用
- 不支持多个Future合并
- 不支持异常处理
线程依赖
- 第二个处理第一个返回的结果
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
System.out.println("第一个任务");
return 10;
}).thenApply(integer -> {
System.out.println("第二个任务");
return integer * integer;
});
System.out.println(cf.get());
combine
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
return 10;
});
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
return 20;
});
CompletableFuture<ArrayList<Integer>> cf = cf1.thenCombine(cf2, (integer, integer2) -> {
ArrayList<Integer> list = new ArrayList<>();
list.add(integer);
list.add(integer2);
return list;
});
System.out.println(cf.get());