基础篇:高并发一瞥,线程和线程池的总结(下篇)

老老老JR老北
发布于 2023-12-5 10:45
浏览
0收藏

8 Executors的四种线程池浅析

  • 「newFixedThreadPool」
  • 指定核心线程数,队列是LinkedBlockingQueue无界阻塞队列,永远不可能拒绝任务;适合用在稳定且固定的并发场景,建议线程设置为CPU核数

//Executors.java
    public     static ExecutorService newFixedThreadPool(    int{
        return     new ThreadPoolExecutor(nThreads, nThreads,
                        0L, TimeUnit.MILLISECONDS,
                            new LinkedBlockingQueue<Runnable>());
}
  • 「newCachedThreadPool」
  • 核心池大小为0,线程池最大线程数为最大整型,任务提交先加入到阻塞队列中,非核心线程60s没任务执行则销毁,阻塞队列为SynchronousQueue。newCachedThreadPool会不断的创建新线程来执行任务,不建议用

//Executors.java
    public     static ExecutorService newCachedThreadPool(){
        return     new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                  60L, TimeUnit.SECONDS,
                      new SynchronousQueue<Runnable>());
}
  • 「newScheduledThreadPool」
  • ScheduledThreadPoolExecutor(STPE)其实是ThreadPoolExecutor的子类,可指定核心线程数,队列是STPE的内部类DelayedWorkQueue。「STPE的好处是 A 延时可执行任务,B 可执行带有返回值的任务」

//Executors.java
    public ScheduledThreadPoolExecutor(    int corePoolSize,
                                   ThreadFactory threadFactory){
        super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue(), threadFactory);
}
//指定延迟执行时间    
    public <V> ScheduledFuture<V> 
schedule(Callable<V> callable,     long    
  • 「newSingleThreadExecutor」
  • 和newFixedThreadPool构造方法一致,不过线程数被设置为1了。SingleThreadExecutor比new个线程的好处是;「线程运行时抛出异常的时候会有新的线程加入线程池完成接下来的任务;阻塞队列可以保证任务按FIFO执行」

//Executors.java
    public     static ExecutorService newSingleThreadExecutor(){
        return     new FinalizableDelegatedExecutorService
        (    new ThreadPoolExecutor(1, 1,
                  0L, TimeUnit.MILLISECONDS,
                      new LinkedBlockingQueue<Runnable>())); //无界队列
}

9 如果优雅地关闭线程池

  • 线程池的关闭,就要先关闭池中的线程,上文第三点有提,暴力强制性stop线程会导致同步数据的不一致,因此我们要调用interrupt关闭线程
  • 而线程池提供了两个关闭方法,shutdownNow和shuwdown
  • shutdownNow:线程池拒接收新任务,同时立马关闭线程池(执行中的会继续执行完),队列的任务不再执行,返回未执行任务List

    public List<Runnable> shutdownNow(){
    ...
        final ReentrantLock mainLock =     this.mainLock;
    mainLock.lock(); //加锁
        try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers(); //interrupt关闭线程
        tasks = drainQueue(); //未执行任务
    ...    
  • shuwdown:线程池拒接收新任务,同时等待线程池里的任务执行完毕后关闭线程池,代码和shutdownNow类似就不贴了

10 线程池为什么使用的是阻塞队列

先考虑下为啥线程池的线程不会被释放,它是怎么管理线程的生命周期的呢

//ThreadPoolExecutor.Worker.class
    final     void runWorker(Worker w){
    ...
    //工作线程会进入一个循环获取任务执行的逻辑
        while (task !=     null || (task = getTask()) !=     null)
    ...
}

    private Runnable getTask(){
    ...
    Runnable r = timed ? 
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 
        : workQueue.take(); //线程会阻塞挂起等待任务,
    ...    
}

可以看出,无任务执行时,线程池其实是利用阻塞队列的take方法挂起,从而维持核心线程的存活

11 线程池的worker继承AQS的意义

//Worker class,一个worker一个线程
Worker(Runnable firstTask) {
    //禁止新线程未开始就被中断
    setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(    this);
}

    final     void runWorker(Worker w){
    ....
    //对应构造Worker是的setState(-1)
    w.unlock(); // allow interrupts
        boolean completedAbruptly =     true;
        ....
        w.lock(); //加锁同步
        ....
            try {
            ...
            task.run();
            afterExecute(task,     null);
        }     finally {
            ....
            w.unlock(); //释放锁
        }

worker继承AQS的意义:A 禁止线程未开始就被中断;B 同步runWorker方法的处理逻辑

12 拒绝策略

  • AbortPolicy「丢弃任务并抛出RejectedExecutionException异常」
  • DiscardOldestPolicy「丢弃队列最前面的任务,然后重新提交被拒绝的任务」
  • DiscardPolicy「丢弃任务,但是不抛出异常」
  • CallerRunsPolicy

“A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.”

如果任务被拒绝了,则由「提交任务的线程」执行此任务

13 ForkJoinPool了解一波

基础篇:高并发一瞥,线程和线程池的总结(下篇)-鸿蒙开发者社区

ForkJoinPool和ThreadPoolExecutor不同,它适合执行可以分解子任务的任务,如树的遍历,归并排序等一些递归场景

基础篇:高并发一瞥,线程和线程池的总结(下篇)-鸿蒙开发者社区

基础篇:高并发一瞥,线程和线程池的总结(下篇)-鸿蒙开发者社区

  • ForkJoinPool每个线程有一个对应的双端队列deque;当线程中的任务被fork分裂,分裂出来的子任务会放入线程自己的deque,减少线程的竞争
  • work-stealing工作窃取算法

基础篇:高并发一瞥,线程和线程池的总结(下篇)-鸿蒙开发者社区

当线程执行完自己deque的任务,且其他线程deque还有多的任务,则会启动窃取策略,从其他线程deque队尾获取线程

  • 使用RecursiveTask实现forkjoin流程demo

    public     class ForkJoinPoolTest {
        public     static     void main(String[] args)     throws{
        ForkJoinPool forkJoinPool =     new ForkJoinPool();
            for (    int i = 0; i < 10; i++) {
            ForkJoinTask task = forkJoinPool.submit(    new Fibonacci(i));
            System.out.println(task.get());
        }
    }
        static     class Fibonacci     extends RecursiveTask<Integer> {
            int n;
            public Fibonacci(    int{      this.n = n;  }
        @Override
            protected Integer compute(){
                if (n <= 1) {     return n; }
            Fibonacci fib1 =     new Fibonacci(n - 1);
            fib1.fork(); //相当于开启新线程执行
            Fibonacci fib2 =     new Fibonacci(n - 2);
            fib2.fork(); //相当于开启新线程执行
                return fib1.join() + fib2.join(); //合并阻塞返回结果
        }
    }
}

参考文章

  • Java线程和操作系统线程的关系[1]
  • 线程的3种实现方式[2]
  • 如何优雅的关闭Java线程池[3]
  • Java程序员必备的一些流程图[4]
  • JDK提供的四种线程池[5]
  • 7种阻塞队列相关整理[6]
  • 六种常见的线程池含ForkJoinPool[7]


文章转载自公众号:潜行前行

分类
标签
已于2023-12-5 10:45:28修改
收藏
回复
举报
回复
    相关推荐