一、线程池

1. 简介

线程的创建和销毁,都涉及到系统调用,比较消耗系统资源,所以就引入了线程池技术,避免频繁的线程创建和销毁。

合理地使用线程池能够带来的好处:

1) 通过重复利用已创建的线程降低资源消耗;

2) 提高任务的响应速度;

3) 使用线程池可以对线程进行统一分配、调优和监控。

2. 实现原理

线程池是一组线程的集合,当提交一个新任务到线程池时,处理流程大致如下:

1) 线程池判断核心线程池里的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。
2) 如果核心线程池里的线程都在执行任务,判断工作队列是否已经满,如果没有,则将任务存储在工作任务中。
3) 如果工作队列满了,判断线程池的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务,如果满了,则交给饱和策略来处理这个任务。

线程池 并行计算 合并结果 java 线程池并发_ci

3. Executor框架

        Java的线程既是工作单元,也是执行机制。从JDK 5开始,把工作单元与执行机制分离开来。工作单元包括Runnable和Callable,而执行机制由Executor框架提供,框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。

3.1 类继承体系

线程池 并行计算 合并结果 java 线程池并发_阻塞队列_02

1) Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。

public interface Executor {
    void execute(Runnable command);
}

2) ExecutorService接口定义了线程池的具体行为:

void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

3) AbstractExecutorService子类:实现ExecutorService接口,为各类执行器类提供基础。

4)ScheduledExecutorService接口:添加了处理延迟执行或者周期任务。

5)ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。

6) ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执
行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。

框架使用示意图:

线程池 并行计算 合并结果 java 线程池并发_线程池 并行计算 合并结果 java_03

  • Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
  • Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。
  • execute适用于不需要关注返回值的场景,只需要将线程丢到线程池中去执行就可以了。
  • submit方法适用于需要关注返回值的场景

3.2 ThreadPoolExecutor详解

        Executor框架最核心的类是ThreadPoolExecutor,是线程池的主要实现类,被称为可重用固定线程数的线程池。主要由下列4个组件构成。

  • corePool:核心线程池的大小。
  • maximumPool:最大线程池的大小。
  • BlockingQueue:用来暂时保存任务的工作队列。
  • RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler。

3.2.1 核心数据结构

        a. 状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED

// runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS; // 接受新任务, 且处理已经进入阻塞队列的任务
    private static final int SHUTDOWN   =  0 << COUNT_BITS; // 不接受新任务, 但处理已经进入阻塞队列的任务
    private static final int STOP       =  1 << COUNT_BITS; // 不接受新任务, 且不处理已经进入阻塞队列的任务, 同时中断正在运行的任务
    private static final int TIDYING    =  2 << COUNT_BITS; // 所有任务都已终止, 工作线程数为0, 线程转化为TIDYING状态并准备调用terminated方法
    private static final int TERMINATED =  3 << COUNT_BITS; //terminated方法已经执行完成

线程池 并行计算 合并结果 java 线程池并发_阻塞队列_04

        b. Worker内部类

        每一个Worker代表一个线程。Worker继承于AQS,也就是说Worker本身就是一把锁,用
于线程池的关闭、线程执行任务的过程中。

public class ThreadPoolExecutor extends AbstractExecutorService {
    //...
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // 存放任务的阻塞队列
    private final BlockingQueue<Runnable> workQueue;
    // 对线程池内部各种变量进行互斥访问控制
    private final ReentrantLock mainLock = new ReentrantLock();
    // 线程集合
    private final HashSet<Worker> workers = new HashSet<Worker>();
    //...

    private final class Worker extends AbstractQueuedSynchronizer implements
Runnable {
        // ...
        final Thread thread; // Worker封装的线程
        Runnable firstTask; // Worker接收到的第1个任务
        volatile long completedTasks; // Worker执行完毕的任务个数
        // ...
    }
}

3.2.2 核心配置参数解析

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize:在线程池中始终维护的线程个数。
  • maxPoolSize:在corePooSize已满、队列也满的情况下,扩充线程至此值。
  • keepAliveTime/TimeUnit:maxPoolSize 中的空闲线程,销毁所需要的时间,总线程数收缩回corePoolSize。
  • workQueue:用于保存等待执行的任务的阻塞队列。常使用的三种队列:1)有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;2)无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;3)直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
  • threadFactory:线程创建工厂,可以自定义,有默认值Executors.defaultThreadFactory()。
  • RejectedExecutionHandler:corePoolSize已满,队列已满,maxPoolSize 已满,最后的拒绝策略。

3.2.3 任务执行过程

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));    
    
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 如果当前线程数小于corePoolSize,则启动新线程
        if (workerCountOf(c) < corePoolSize) {
            // 添加Worker,并将command设置为Worker线程的第一个任务开始执行
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 如果当前的线程数大于或等于corePoolSize,则调用workQueue.offer放入队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 如果线程池正在停止,则将command任务从队列移除,并拒绝command任务请求。
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 放入队列中后发现没有线程执行任务,开启新线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 线程数大于maxPoolSize,并且队列已满,调用拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

execute方法执行情况:

 1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤
需要获取全局锁)。

2) 如果运行的线程数>=corePoolSize,则将任务加入BlockingQueue;

3)如果队列已满,则创建新任务来处理任务(注意,执行这一步骤需要获取全局锁);

4) 如果当前运行的线程超过了maximumPoolSize,任务将拒接,并调用RejectedExecutionHandler.rejectedExecution()方法。

流程图如下:

线程池 并行计算 合并结果 java 线程池并发_线程池_05

3.2.4 线程池的优雅关闭

线程池有两个关闭方法,shutdown()和shutdownNow(),这两个方法会让线程池切换到不同的状
态。在队列为空,线程池也为空之后,进入TIDYING 状态;最后执行一个钩子方法terminated(),进入TERMINATED状态,线程池才真正关闭。

在调用 shutdown()或者shutdownNow()之后,线程池并不会立即关闭,接下来需要调用 awaitTermination() 来等待线程池关闭。关闭线程池的正确步骤:

// executor.shutdownNow();
executor.shutdown();
try {
    boolean flag = true;
    do {
        flag = ! executor.awaitTermination(500, TimeUnit.MILLISECONDS);
    } while (flag);
} catch (InterruptedException e) {
    // ...
}

shutdown()与shutdownNow()的区别:

1) shutdown()不会清空任务队列,会等所有任务执行完成,shutdownNow()清空任务队列。

2) shutdown()只会中断空闲的线程,shutdownNow()会中断所有线程。

3.2.5 拒绝策略

在execute(Runnable command)方法中,调用了reject(command)执行拒绝策略。

触发条件:

1)核心线程池满,阻塞队列满,非核心线程池满;

2)ThreadPoolExecutor关闭。

private volatile RejectedExecutionHandler handler;
    
    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }

RejectedExecutionHandler 是一个接口,定义了四种实现,分别对应四种不同的拒绝策略:

 AbortPolicy::丢弃任务并抛出RejectedExecutionException(默认使用);

CallerRunsPolicy:调用者直接在自己的线程里执行,线程池不处理;

DiscardOldestPolicy:阻塞队列丢弃最近任务,执行当前任务;

DiscardPolicy:空运转,什么都不干。

线程池核心数设置:

1.先看下机器的CPU核数,然后在设定具体参数:

System.out.println(Runtime.getRuntime().availableProcessors());

即CPU核数 = Runtime.getRuntime().availableProcessors()

2.分析下线程池处理的程序是CPU密集型,还是IO密集型

CPU密集型:核心线程数 = CPU核数 + 1

IO密集型:核心线程数 = CPU核数 * 2

注:IO密集型(某大厂实践经验)

       核心线程数 = CPU核数 / (1-阻塞系数)     例如阻塞系数 0.8,CPU核数为4

       则核心线程数为20

3.3 Executors工具类

通过Executor框架的工具类Executors,可以创建各种不同类型的ThreadPoolExecutor。

a. newSingleThreadExecutor 单线程的线程池

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

b. newFixedThreadPool 固定线程数的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

c. newCachedThreadPool 可缓存的线程池

每接收一个请求,就创建一个线程来执行。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

d. newScheduledThreadPool 周期调度的线程池

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

e. newWorkStealingPool (JDK1.8新增) 具有抢占式操作的线程池

public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

二、Java中的阻塞队列

1. 什么是阻塞队列

阻塞队列BlockingQueue继承了Queue 接口,是一个支持阻塞的插入和移除的队列。

阻塞队列的典型使用场景就是 生产者/消费者模式。生产者是向队列添加元素的线程;消费者是从队列取元素的线程。

在阻塞队列不可用时,队列操作有以下四种处理方式:

方法\处理方式

抛出异常

返回特殊值

一直阻塞

超时退出

插入方法

add(e)

offer(e)

put(e)

offer(e,time,unit)

移除方法

remove()

poll()

take()

poll(time,unit)

检查方法

element()

peek()

   ——

        ——

2. 常见的阻塞队列

  2.1 ArrayBlockingQueue

ArrayBlockingQueue 是最典型的有界队列,其内部是用数组存储元素的,利用 ReentrantLock 实现线程安全,使用 Condition 来阻塞和唤醒线程。

public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

参数fair表示是否指定公平,默认不保证线程公平的访问线程,即则塞的线程在队列可用时都可以争夺访问队列的资格,可能存在插队。反之公平则是按照阻塞的先后顺序访问队列,为了保证公平性,通常会降低吞吐量。

2.2 LinkedBlockingQueue

LinkedBlockingQueue是一个用链表实现的无界阻塞队列。队列的默认最大长度为Integer.MAX_VALUE。同样利用 ReentrantLock 实现线程安全,使用 Condition 来阻塞和唤醒线程。无法设置ReentrantLock 的公平非公平,默认是非公平。

public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

2.3. PriorityBlockingQueue

PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采取自然顺序
升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则。或者初始化
时,指定构造参数Comparator来对元素进行排序。

public PriorityBlockingQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.comparator = comparator;
        this.queue = new Object[Math.max(1, initialCapacity)];
    }

2.4 DelayQueue

DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素的时候,可以指定多久才能从队列获取当前元素。只有在延迟期满时才能从队列中提取元素。

队列获取数据,first为空表示队列为空或者等待时间没有耗尽直接返回空

public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E first = q.peek();
            return (first == null || first.getDelay(NANOSECONDS) > 0)
                ? null
                : q.poll();
        } finally {
            lock.unlock();
        }
    }

2.5 SynchronousQueue

SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。SynchronousQueue 的容量不是 1 而是 0,因为 SynchronousQueue 不需要去持有元素,它所做的就是直接传递(direct handoff)。SynchronousQueue的吞吐量高于
LinkedBlockingQueue和ArrayBlockingQueue。

public SynchronousQueue() {
        this(false);
    }
    
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }

    public E peek() {
        return null;
    }

2.6 LinkedTransferQueue

LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,多了tryTransfer和transfer方法。

LinkedTransferQueue采用一种预占模式。意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的方法返回。我们称这种节点操作为“匹配”方式。

2.7 LinkedBlockingDeque

LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。相比其他的阻塞队列,LinkedBlockingDeque多了addFirst、addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以First单词结尾的方法,表示插入、获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。