Java并发ThreadPoolExecutor+BlockingQueue使用详解

前言

从Java线程池及用法 一文中我们知道,Java提供了4个常用的线程池。但是在如下场景的的时候,就需要我们使用自定义ThreadPoolExecutor和BlockingQueue队列结合使用来处理。

  1. 需要的子线程数量很多,但是数量不确定。
  2. 子线程有自己的优先级,根据优先级来确定执行的先后顺序。
  3. 监听线程池的开始,结束,关闭等状态。

ThreadPoolExecutor

构造方法

//使用给定的初始参数和默认线程工厂以及拒绝的执行处理程序创建新的ThreadPoolExecutor。
//使用Executors工厂方法之一代替此通用构造函数可能更方便。
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

//使用给定的初始参数和默认拒绝执行处理程序创建新的ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory)
//使用给定的初始参数和默认线程工厂创建一个新的code ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)
//使用给定的初始参数创建一个新的ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

主要参数

  • corePoolSize:核心线程数
    核心线程会一直存活,及时没有任务需要执行。当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。
  • maxPoolSize:最大线程数
    当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务。
    当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常。
  • keepAliveTime:非核心线程闲置时的超时时长
    超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true 时,keepAliveTime 同样会作用于核心线程。
  • unit:用于指定 keepAliveTime 参数的时间单位
    常用的有 TimeUnit .MILLISECONDS 和 TimeUnit .SECONDS。
  • workQueue:线程池中的任务队列
    通过线程池的 execute 方法提交的 Runnable 对象会存储在这个参数中。
  • threadFactory:线程工厂
    为线程池提供创建新的线程的功能。threadFactory 是一个接口,它只有一个方法: public abstract Thread newThread (Runnable r);
  • RejectedExecutionHandler:通常叫做拒绝策略
    在线程池已经关闭的情况下或者当线程数已经达到maxPoolSize且队列已满。只要满足其中一种时,在使用execute() 来提交新的任务时将会拒绝,而默认的拒绝策略是抛一个 RejectedExecutionException 异常。

ThreadPoolExecutor执行顺序:

  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数,且任务队列已满
    1. 若线程数小于最大线程数,创建线程
    2. 若线程数等于最大线程数,抛出异常,拒绝任务

BlockingQueue

BlockingQueue是一个特殊的队列,当我们从BlockingQueue中取数据时,如果BlockingQueue是空的,
则取数据的操作会进入到阻塞状态,当 BlockingQueue 中有了新数据时,这个取数据的操作又会被重新唤醒。
同理,如果 BlockingQueue 中的数据已经满了,往 BlockingQueue 中存数据的操作又会进入阻塞状态,直到 BlockingQueue 中又有新的空间,存数据的操作又会被重新唤醒。它的泛型限定它是用来存放 Runnable 对象的。

几种常用的BlockingQueue

  • ArrayBlockingQueue
    这个表示一个规定了大小的 BlockingQueue,ArrayBlockingQueue 的构造函数接受一个 int 类型的数据,该数据表示BlockingQueue 的大小,存储在 ArrayBlockingQueue 中的元素按照 FIFO(先进先出)的方式来进行存取。
  • LinkedBlockingQueue
    这个表示一个大小不确定的 BlockingQueue,在LinkedBlockingQueue 的构造方法中可以传一个 int 类型的数据,这样创建出来的 LinkedBlockingQueue是有大小的,默认LinkedBlockingQueue的大小就为 Integer.MAX_VALUE。
  • PriorityBlockingQueue
    这个队列和 LinkedBlockingQueue 类似,不同的是PriorityBlockingQueue 中的元素不是按照 FIFO 来排序的,而是按照元素的Comparator 来决定存取顺序的(这个功能也反映了存入 PriorityBlockingQueue 中的数据必须实现了 Comparator 接口)。
  • SynchronousQueue
    这个是同步 Queue,属于线程安全的 BlockingQueue的一种,在 SynchronousQueue 中,生产者线程的插入操作必须要等待消费者线程的移除操作,Synchronous 内部没有数据缓存空间,因此我们无法对 SynchronousQueue进行读取或者遍历其中的数据,元素只有在你试图取走的时候才有可能存在。我们可以理解为生产者和消费者互相等待,等到对方之后然后再一起离开。

常用线程池对应的BlockingQueue

  1. newFixedThreadPool()—>LinkedBlockingQueue
  2. newSingleThreadExecutor()—>LinkedBlockingQueue
  3. newCachedThreadPool()—>SynchronousQueue
  4. newScheduledThreadPool()—>DelayedWorkQueue

使用代码示例

public class Main {
    public static void main(String[] args) {

        BlockingQueue bq = new ArrayBlockingQueue(5);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 30, 10, TimeUnit.SECONDS, bq);
        executor.allowCoreThreadTimeOut(true);
        for (int i = 0; i < 30; i++) {
            executor.execute(new PrintDate());
        }
    }

    public static class PrintDate implements Runnable {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + ":" + System.currentTimeMillis());
        }
    }
}

如何设置参数

默认值
corePoolSize=1
queueCapacity=Integer.MAX_VALUE
maxPoolSize=Integer.MAX_VALUE
keepAliveTime=60s
allowCoreThreadTimeout=false
rejectedExecutionHandler=AbortPolicy()
如何来设置
tasks :每秒的任务数,假设为500~1000
taskcost:每个任务花费时间,假设为0.1s
responsetime:系统允许容忍的最大响应时间,假设为1s
  • corePoolSize = 每秒需要多少个线程处理?
    • threadcount = tasks/(1/taskcost) =tasks·taskcout = (500~1000)·0.1 = 50~100 个线程。corePoolSize设置应该大于50
    • 根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可
  • queueCapacity = (coreSizePool/taskcost)*responsetime
    • 计算可得 queueCapacity = 80/0.1*1 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
    • 切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
  • maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
    • 计算可得 maxPoolSize = (1000-80)/10 = 92
    • 最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
  • rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
  • keepAliveTime和allowCoreThreadTimeout采用默认通常能满足。

扩展

线程池状态的监听

ThreadPoolExecutor 也向外提供了三个接口供我们自己扩展满足我们需求的线程池,这三个接口分别是:

  1. beforeExecute() - 任务执行前执行的方法
  2. afterExecute() -任务执行结束后执行的方法
  3. terminated() -线程池关闭后执行的方法
public class MyThreadPool extends ThreadPoolExecutor {

    private static final String TAG = "Bradley" ;

    public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        Log.d(TAG, "beforeExecute: 开始执行任务!");
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        Log.d(TAG, "beforeExecute: 任务执行结束!");
    }

    @Override
    protected void terminated() {
        super.terminated();
        Log.d(TAG, "terminated: 线程池关闭!");
    }
}

优先级线程池

1.创建一个实现 Runnable 接口的类,并向外提供一个抽象方法供我们实现自定义功能,并实现 Comparable 接口,实现这个接口主要就是进行优先级的比较.

public abstract class PriorityRunnable implements Comparable<PriorityRunnable> ,Runnable{

    private int priority;

    public PriorityRunnable(int priority) {
        if (priority < 0)
            throw new IllegalArgumentException();
        this.priority = priority;
    }

    @Override
    public int compareTo(PriorityRunnable another) {
        int my = this.getPriority();
        int other = another.getPriority();
        return my < other ? 1 : my > other ? -1 : 0;
    }

    @Override
    public void run() {
        doSth();
    }

    public abstract void doSth();

    public int getPriority() {
        return priority;
    }
}

2.使用PriorityBlockingQueue队列

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, new PriorityBlockingQueue<Runnable>());
        for (int i = 0; i < 30; i++) {
            final int priority = i;
            PriorityRunnable runnable = new PriorityRunnable(priority) {
                @Override
                public void doSth() {
                    String threadName = Thread.currentThread().getName();
                    Log.v(TAG, "线程:" + threadName + ",正在执行优先级为:" + priority + "的任务");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            executor.execute(runnable);
        }
    }

参考

Android 自定义线程池
ThreadPoolExecutor线程池参数设置技巧

你可能感兴趣的:(#,Java笔记)