Java线程池详解(一)-创建自有线程池

什么是线程池

线程池的概念大家应该都很清楚,帮我们重复管理线程,避免创建大量的线程增加开销。除了降低开销以外,线程池也可以提高响应速度

创建线程池需要使用 ThreadPoolExecutor 类,它的构造函数参数如下:

public ThreadPoolExecutor(int corePoolSize,    //核心线程的数量
                          int maximumPoolSize,    //最大线程数量
                          long keepAliveTime,    //超出核心线程数量以外的线程空余存活时间
                          TimeUnit unit,    //存活时间的单位
                          BlockingQueue<Runnable> workQueue,    //保存待执行任务的队列
                          ThreadFactory threadFactory,    //创建新线程使用的工厂
                          RejectedExecutionHandler handler // 当任务无法执行时的处理器
                          ) {...}
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
 
    int c = ctl.get();
    //1.当前池中线程比核心数少,新建一个线程执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2.核心池已满,但任务队列未满,添加到队列中
    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);
    }
    //3.核心池已满,队列已满,试着创建一个新线程
    else if (!addWorker(command, false))
        reject(command);    //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}

可以看到,线程池处理一个任务主要分三步处理,代码注释里已经介绍了,我再用通俗易懂的例子解释一下:

(线程比作员工,线程池比作一个团队,核心池比作团队中核心团队员工数,核心池外的比作外包员工)

  1. 有了新需求,先看核心员工数量超没超出最大核心员工数,还有名额的话就新招一个核心员工来做 需要获取全局锁

  2. 核心员工已经最多了,HR 不给批 HC 了,那这个需求只好攒着,放到待完成任务列表吧

  3. 如果列表已经堆满了,核心员工基本没机会搞完这么多任务了,那就找个外包吧 需要获取全局锁

  4. 如果核心员工 + 外包员工的数量已经是团队最多能承受人数了,没办法,这个需求接不了了

 由于 1 和 3 新建线程时需要获取全局锁,这将严重影响性能。因此 ThreadPoolExecutor 这样的处理流程是为了在执行 execute() 方法时尽量少地执行 1 和 3,多执行 2。

在 ThreadPoolExecutor 完成预热后(当前线程数不少于核心线程数),几乎所有的 execute() 都是在执行步骤 2。

ThreadPoolExecutor 构造函数的参数

  1. corePoolSize:核心线程池数量

  1. 在线程数少于核心数量时,有新任务进来就新建一个线程,即使有的线程没事干

  2. 等超出核心数量后,就不会新建线程了,空闲的线程就得去任务队列里取任务执行了

  1. maximumPoolSize:最大线程数量

  1. 包括核心线程池数量 + 核心以外的数量

  2. 如果任务队列满了,并且池中线程数小于最大线程数,会再创建新的线程执行任务

  1. keepAliveTime:核心池以外的线程存活时间,即没有任务的外包的存活时间

  1. 如果给线程池设置 allowCoreThreadTimeOut(true),则核心线程在空闲时头上也会响起死亡的倒计时

  2. 如果任务是多而容易执行的,可以调大这个参数,那样线程就可以在存活的时间里有更大可能接受新任务

  1. workQueue:保存待执行任务的阻塞队列

  1. 不同的任务类型有不同的选择

  1. threadFactory:每个线程创建的地方

  1. 可以给线程起个好听的名字,设置个优先级啥的

  1. handler:饱和策略,大家都很忙,咋办呢,有四种策略

  1. CallerRunsPolicy:只要线程池没关闭,就直接用调用者所在线程来运行任务

  2. AbortPolicy:直接抛出 RejectedExecutionException 异常

  3. DiscardPolicy:悄悄把任务放生,不做了

  4. DiscardOldestPolicy:把队列里待最久的那个任务扔了,然后再调用 execute() 试试看能行不

  5. 我们也可以实现自己的 RejectedExecutionHandler 接口自定义策略,比如如记录日志什么的

保存待执行任务的阻塞队列

当线程池中的核心线程数已满时,任务就要保存到队列中了。线程池中使用的队列是 BlockingQueue 接口,常用的实现有如下几种:

  1. ArrayBlockingQueue:基于数组、有界,按 FIFO(先进先出)原则对元素进行排序

  2. LinkedBlockingQueue:基于链表,按FIFO (先进先出) 排序元素 吞吐量通常要高于 ArrayBlockingQueue Executors.newFixedThreadPool() 使用了这个队列

  3. SynchronousQueue:不存储元素的阻塞队列 每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态 吞吐量通常要高于 LinkedBlockingQueue Executors.newCachedThreadPool使用了这个队列

  4. PriorityBlockingQueue:具有优先级的、无限阻塞队列

创建线程池

1、先定义线程池的几个关键属性的值:
  1. 设置核心池的数量为 CPU 数的两倍,一般是 4、8,好点的 16 个线程

  2. 最大线程数设置为 64

  3. 空闲线程的存活时间设置为 1 秒

如何设置核心线程数
  1. IO密集型 CPU处理器个数*2

IO密集型,是指系统大部分时间在跟I/O交互,而这个时间线程不会占用CPU来处理

  1. CPU密集型 CPU处理器个数+1

CPU密集型,是指系统大部分时间是在做程序正常的计算任务,例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要CPU来完成。

2、根据处理的任务类型选择不同的阻塞队列

如果是要求高吞吐量的,可以使用 SynchronousQueue 队列;如果对执行顺序有要求,可以使用 PriorityBlockingQueue;如果最大积攒的待做任务有上限,可以使用 LinkedBlockingQueue。

3、创建自己的 ThreadFactory

在其中为每个线程设置个名称

4、创建线程池
public class ThreadPoolManager {
    private final String TAG = this.getClass().getSimpleName();
    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; // 核心线程数为 CPU数*2
    private static final int MAXIMUM_POOL_SIZE = 64;    // 线程队列最大线程数
    private static final int KEEP_ALIVE_TIME = 1;    // 保持存活时间 1秒
 
    private final BlockingQueue<Runnable> mWorkQueue = new LinkedBlockingQueue<>(128);
 
    private final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
 
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r, TAG + " #" + mCount.getAndIncrement());
            thread.setPriority(Thread.NORM_PRIORITY);
            return thread;
        }
    };
 
    private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,
            TimeUnit.SECONDS, mWorkQueue, DEFAULT_THREAD_FACTORY,
            new ThreadPoolExecutor.DiscardOldestPolicy());
 
    private static volatile ThreadPoolManager mInstance = new ThreadPoolManager();
 
    public static ThreadPoolManager getInstance() {
        return mInstance;
    }
 
    public void addTask(Runnable runnable) {
        mExecutor.execute(runnable);
    }
 
    @Deprecated
    public void shutdownNow() {
        mExecutor.shutdownNow();
    }
}
◆ ◆ ◆  ◆ ◆
关注并后台回复 “面试”,即可免费获取
最新2019BAT大厂面试题,更有大数据
视频等你拿

·END·

后端开发技术追求技术的深度

微信号:后端开发技术
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端开发技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值