并发编程笔记-线程池

一、线程池

1. 简介

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

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

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

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

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

2. 实现原理

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

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


3. Executor框架

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

3.1 类继承体系

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更灵活,功能更强大。

框架使用示意图:

  • 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方法已经执行完成

        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()方法。

流程图如下:

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单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值