Java多线程之线程池

1. 创建线程池

建议使用ThreadPoolExecutor类来创建,而不是使用Executors中的静态方法来创建,创建出来的对象属于ExecutorService类型,可以执行各种多线程任务。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
2. 参数详解

按照参数顺序依次是:

  1. corePoolSize:核心线程数。当任务提交时,如果线程池中线程数少于核心线程数,就会创建新线程执行,即使有线程在闲置。,如果多于核心线程数,但少于最大线程数,只有所有线程都在运行才会创建新线程。
  2. maximumPoolSize:最大线程数,线程池允许存在的最大线程数
  3. keepAliveTime:最大空闲时间,当线程池中数量大于corePoolSize时,如果这时没有新任务提交,核心线程外的线程不会立即销毁,而是等待该最大空闲时间后销毁。
  4. unit:最大空间时间的单位。
  5. workQueue:用来保存等待被执行的任务的阻塞队列,任务必须实现Runable接口。Java中实现BlockingQueue的类有:
    • ArrayBlockingQueue:Object[] 有界数组存放,takeIndex和putIndex表示取存索引,FIFO队列
    • LinkedBlockingQuene和LinkedBlockingDeque:基于链表的阻塞队列,FIFO,吞吐量高于数组的
    • SynchronousQuene:不能存储元素,一个线程的insert只能等待另一个线程remove之后执行;remove需要等待另一个insert;
    • priorityBlockingQuene:优先队列,逻辑无界,受限于硬件,元素必须可以比较。无界的原理类似于ArrayList,对Object[]进行扩容。
  6. threadFactory:默认使用Executors.defaultThreadFactory()来创建线程,创建的线程具有相同的优先级,且是非守护线程,同时也设置了线程名称。
  7. handler:线程池饱和策略,当阻塞队列满了,且没有空闲线程,如果继续提交任务,如何处理多余任务,线程池提供了四种策略,在ThreadPoolExecutor有四个实现RejectedExecutionHandler接口的子类:
    • AbortPolicy:默认策略,直接抛出异常
    • CallerRunsPolicy:调用者线程来执行,如果线程池关闭,则丢弃
    • DiscardOldestPolicy:丢弃最早的未执行任务,然后执行当前任务
    • DiscardPolicy:直接丢弃策略
3. 线程池监控
public long getTaskCount() //线程池已执行与未执行的任务总数
public long getCompletedTaskCount() //已完成的任务数
public int getPoolSize() //线程池当前的线程数
public int getActiveCount() //线程池中正在执行任务的线程数量
4. 线程池原理

执行原理

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         * 如果少于核心线程数,创建新线程,并将其作为第一个任务来执行。
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         * 如果任务可以被成功加入队列,仍然需要双检锁是否需要增加线程。
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         * 如果不能加入队列,然后尝试添加一个新的线程。失败了是因为在关闭线程池或者队列饱和,因此需要拒绝任务。
         */
        int c = ctl.get();  // ctl有线程池状态,和活动线程数
        if (workerCountOf(c) < corePoolSize) { // 工作线程数小于核心数时
            if (addWorker(command, true)) // true表示使用coreSize,false表示使用maxSize判断
                return;
            c = ctl.get(); // 失败重新获取ctl
        }
        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);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

简单来说,在执行execute()方法时如果状态一直是RUNNING时,的执行过程如下:

  1. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;
  2. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;
  3. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;
  4. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
    这里要注意一下addWorker(null, false);,也就是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,所以worker在执行的时候,会直接从workQueue中获取任务。所以,在workerCountOf(recheck) == 0时执行addWorker(null, false);也是为了保证线程池在RUNNING状态下必须要有一个线程来执行任务。

addWorker方法
addWorker方法的主要工作是在线程池中创建一个新的线程并执行,firstTask参数 用于指定新增的线程执行的第一个任务,core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,代码如下:

// 根据运行状态和界限(根据core选择coreSize或maxSize),来选择是否增加worker执行任务
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            // 如果rs关闭,不再接收任务,且需要满足(队列为空,或者任务为null),都不增加worker
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry; // 尝试增加worker数,如果成功,跳出最外层for循环
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask); // 新建一个worker,每一个worker工作在一个线程上
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock(); // 使用可重入锁,
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());
					// 如果是运行状态,或者关闭状态但是还要执行队列里的任务,
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w); // 线程其实是一个HashSet集合
                        int s = workers.size();
                        if (s > largestPoolSize) // largesPoolSize保存线程池中出现过的最大线程数量
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start(); // 启动线程
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

Worker类
线程池中的每一个线程被封装成一个Worker对象,ThreadPool维护的其实就是一组Worker对象,(使用HashSet数据结构保存)请参见JDK源码。
Worker类继承了AQS,并实现了Runnable接口,注意其中的firstTask和thread属性:firstTask用它来保存传入的任务;thread是在调用构造方法时通过ThreadFactory来创建的线程,是用来处理任务的线程。
Worker继承了AQS,使用AQS来实现独占锁的功能。
在Worker类中的run方法调用了runWorker方法来执行任务,runWorker是线程池类的方法,通过while循环不断通过getTask()方法来获取任务,调用task.run()方法来执行任务。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值