【多线程】线程池

文章目录

1 架构说明

  • Java中线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。
  • 注意对比:数组:Array–>Arrays;集合类:Collection–>Collections; 线程池:Executor–>Executors。加个s表示工具类。
    《【多线程】线程池》

2 线程池的实现

  • Executors.newFixedThreadPool(int n); 创建一个定长的线程池,使用的阻塞队列是LinkedBlockingQueue。用来执行长期的任务,性能好很多。
  • Executors.newSingleThreadExecutor();创建一个单线程化的线程池,使用的阻塞队列是LinkedBlockingQueue。用于一个任务一个任务执行的场景。
  • Executors.newCachedThreadPool();创建一个可缓存的线程池,使用的阻塞队列是SynchronousQueue。用于执行很多短期异步的小程序或者负载较轻的服务器。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo { 
    public static void main(String[] args) { 
        // 一池5个处理线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
        // 一池1个处理线程
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
        // 一池n个处理线程
        ExecutorService threadPool = Executors.newCachedThreadPool();
        try { 
            for (int i = 1; i <= 10; i++) { 
                threadPool.execute(() -> { 
                    System.out.println(Thread.currentThread().getName() + "\t is running");
                });
// try { 
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) { 
// e.printStackTrace();
// }
            }
        } catch (Exception e) { 
            e.printStackTrace();
        } finally { 
            threadPool.shutdown();
        }
    }
}

3 线程池优势

线程复用控制最大并发数管理线程

  • 其他池化技术:线程池、数据库连接池、Http连接池等等,都是为了减少获取资源的消耗,提高对资源的利用效率

4 线程池的7个重要参数

  • 1 corePoolSize:线程池中的常驻核心线程数
  • 2 maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
  • 3 keepAliveTime:多余的空闲线程的存活时间。
    当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
  • 4 unit:keepAliveTime的单位。
  • 5 workQueue:任务队列,被提交但尚未被执行的任务。
  • 6 threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程。一般用默认的即可。
  • 7 handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时,如何拒绝请求执行的策略
    AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。
    CallerRunsPolicy:”调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
    DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
    DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
    以上拒绝策略都实现了RejectedExecutionHandler接口。

《【多线程】线程池》

  • 线程池工作原理:先启用corePool中的线程,当任务还在增加时,安排至阻塞队列中等待。当阻塞队列满了后,且任务还在增加,会启动maximumPool。当maximumPool中的线程全部启用后:(1)任务还在增加,且阻塞队列已满,则根据拒绝策略,拒绝任务执行。(2)任务不再增加,根据存活时间关闭maximumPool中,非corePool的线程。

  • 线程池的主要流程(三层判断)
    《【多线程】线程池》

  • 线程池执行流程小总结:
    《【多线程】线程池》

5 示例

MyRunnable.java

import java.util.Date;
public class MyRunnable implements Runnable { 
    private String command;

    public MyRunnable(String command) { 
        this.command = command;
    }

    @Override
    public void run() { 
        System.out.println(Thread.currentThread().getName() + " Start.Time = " + new Date());
        // 这个线程执行5秒钟左右
        processCommand();
        System.out.println(Thread.currentThread().getName() + " End.Time = " + new Date());
    }

    private void processCommand() { 
        try { 
            Thread.sleep(5000);
        } catch (InterruptedException e) { 
            e.printStackTrace();
        }
    }

    @Override
    public String toString() { 
        return this.command;
    }
}

ThreadPoolDemo.java

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo { 
    // 核心线程数为5
    private static final int CORE_POOL_SIZE = 5;
    // 最大线程数为10
    private static final int MAX_POOL_SIZE = 10;
    // 任务队列容量为100
    private static final int QUEUE_CAPACITY = 100;
    // 等待时间为1L
    private static final long KEEP_ALIVE_TIME = 1L;
    public static void main(String[] args) { 
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                // 等待时间的单位为秒
                TimeUnit.SECONDS,
                // 任务队列容量为100
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                // 饱和策略为CallerRunsPolicy
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        for (int i = 0; i < 10; i++) { 
            // 创建线程对象
            Runnable worker = new MyRunnable("" + i);
            // 添加到线程池,执行线程
            executor.execute(worker);
        }
        // 终止线程池
        executor.shutdown();
        while (!executor.isTerminated()) { 
        }
        System.out.println("Finish all threads");
    }
}

说明

  • 通过实现Runnable接口创建线程,通过ThreadPoolExecutor创建线程池

  • 线程池每次会同时执行5个任务,这5个任务执行完后,剩下的五个才会被执行

  • execute()方法和submit()方法区别
    execute()用于提交不需要返回值的任务,因此无法判断任务是否被线程池执行成功
    submit()用于提交需要返回值的任务。线程池会返回一个Future类型的对象,可以判断任务是否执行成功。

6 合理配置线程池的参数(线程池最大线程数)

看业务类型

  • CPU密集型:CPU核心数+1。
  • IO密集型:第一种,如果IO密集型任务线程并不是一直在执行任务,则应该配置进可能多的线程,如CPU核心数*2。第二种,如果该任务需要大量的IO,即大量的阻塞,参考公式:CPU核心数/(1-阻塞系数)。阻塞系数在0.8~0.9之间。例如8核CPU:8/(1-0.9)=80个线程数。

参考资料
1.JavaGuide公众号文章
2.尚硅谷Java大厂面试题全集(java面试,周阳主讲)-Java面试_大厂高频面试题_阳哥

    原文作者:小镇攻城狮
    原文地址: https://blog.csdn.net/Awt_FuDongLai/article/details/116753541
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞