Java-第十六部分-JUC-线程池、分支合并框架和异步回调

270 阅读4分钟

JUC全文

线程池

  • ThreadPool
  • 一种线程使用模式,线程过多会带来调度开销;线程池维护多个线程,等待监督管理者分配可并发执行的任务,保证内核充分利用,防止过度调度
  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性
  • 架构 image.png

submit和excute

  • submit可以传入RunnableCallable接口,如果传入Callable,通过返回的future调用get获取返回值;可以通过get获取异常
  • excute没有返回值;只能在run方法try..catch处理异常

分类

  • 一池N线程
  1. 线程池中的线程处于一定的量,可以很好地控制线程的并发量
  2. 线程可以重复被使用,在显式关闭之前,都将一直存在
  3. 超出一定量的线程,任务被提交时候需要在队列中等待
//一池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),此生产线程必须阻塞
  • newSingleThreadExecutornewFixedThreadPool队列为LinkedBlockingQueue,长度为Integer.MAX_VALUE image.png

底层原理

image.png

  • 执行execute之后才会创建线程
  • 当有任务时,且核心线程空闲,直接调用核心线程
  • 当有任务时,核心线程没有空闲的,先在阻塞队列中进行等待
  • 当阻塞队列满时,有任务时,创建新线程处理任务,不取阻塞队列中的,会插队
  • 当核心线程、阻塞队列、最大线程数满时,对任务进行拒绝

执行优先级和提交优先级

image.png

  • 提交优先级,核心、队列、非核心
  • 执行优先级,核心、非核心、队列
  • 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,默默丢弃无法处理的任务,不予处理也不抛出异常,允许任务丢失

自定义线程

  • 实际开发中需要自定义线程池,避免自带的线程池,其中newSingleThreadExecutornewFixedThreadPool会出现内存溢出(任务队列将会爆满),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();
}

image.png

线程池状态

  • Running,能接受新任务以及处理已添加的任务;
  • Shutdown,不接受新任务,可以处理已经添加的任务,也就是不能再调用execute或者submit了;
  • Stop,不接受新任务,不处理已经添加的任务,并且中断正在处理的任务;
  • Tidying,所有的任务已经终止,CTL记录的任务数量为0,CTL负责记录线程池的运行状态与活动线程数量;
  • Terminated,线程池彻底终止,则线程池转变为terminated的状态。

分支合并框架

  • 递归实现
  • Fork/Join,讲一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的结果

fork,将一个复杂的任务进行分拆 join,把分拆任务的结果合并

  • ForkJoinPool,分支合并池 image.png image.png

案例

  • 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 image.png
//无返回值
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());