线程池原理浅析

前言:

由于互联网的发展,多线程技术应用的越来越多,但是一味的创建线程,会带来性能消耗,而且到一定数量后,有可能使服务器崩溃,由此引进了线程池。

一、线程池的优点

  • 合理的利用线程资源,从而减少线程创建和销毁的次数,降低开销。
  • 根据实际情况合理的调整线程池的参数,可以一定程度上提高系统的吞吐量,提高效率。

二、线程池的创建

现在以java.util.concurrent包中ThreadPoolExecutor类为例来展示。

						ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

具体参数的含义:

  • corePoolSize:核心线程数,即使空闲,也不会被回收。

  • maximumPoolSize:最大线程数。

  • keepAliveTime:当线程数大于线程池中的核心线程数时,非核心线程数能存活的最长时间。

  • unit:时间单位,可选的单位有:DAYS(天),HOURS(小时),MINUTES(分钟),MILLISECONDS(毫秒),MICROSECONDS(微妙)和NANOSECONDS(纳秒)。

  • workQueue:存放任务的队列。BlockingQueue是一种带锁的阻塞队列,常见的队列分为:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue。

    • SynchronousQueue:同步队列。对于工作队列,一个好的默认选择是SynchronousQueue,它将任务交给线程,而不需要持有它们。在同步队列中,如果没有立即可用的线程来运行任务,那么对任务排队的尝试将失败,因此将构造一个新线程。由于以上特性,SynchronousQueue线程容量没有限制,以防止新提交的任务被拒绝。Executors.newCachedThreadPool使用了这个队列。
    • LinkedBlockingQueue:基于链表的无界阻塞队列。当线程池中核心线程满了后,新任务将一直在队列中等待。因此核心线程数等于最大线程数。(maximumPoolSize参数设置了也不会生效。)这种情况适用于每个任务都是独立的,不会相互影响。在web服务器中,如果应用LinkedBlockingQueue,可以很好的缓冲突然间的大量请求。Executors.newFixedThreadPool()使用了这种队列。
    • ArrayBlockingQueue:基于数组的有界阻塞队列。可以防止资源耗尽,但是队列的大小和线程池的大小很难去权衡。
  • threadFactory:当执行程序创建新线程时,使用该工厂。threadFactory分为两种:DefaultThreadFactory和PrivilegedThreadFactory。

    • DefaultThreadFactory:创建一个同线程组且默认优先级的线程,ThreadPoolExecutor默认采用这个。
    • PrivilegedThreadFactory:使用访问权限创建一个权限控制的线程。
  • RejectedExecutionHandler :饱和策略,任务超出线程池容量和队列大小时执行的策略,分为:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy和DiscardPolicy。

    • AbortPolicy:不处理,直接抛出异常,默认策略。
    • CallerRunsPolicy:将重试添加当前的任务,重复调用execute()方法。
    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
    • DiscardPolicy:直接不处理,也不抛出异常。

三、线程池的运行原理

我们调用ThreadPoolExecutor类的execute方法,就可以将自己的运行任务(必须继承Runnable接口)放入线程池中运行。

执行步骤主要有三步:

  1. 若线程池中的线程数少于核心线程数,将创建新的线程来执行当前任务。
    调用的addWorker方法将检查线程池的运行状态、工作线程数
    和新的线程是否创建成功。

  2. 若线程池中的线程数大于核心线程数时,队列还没有满,将当前任务加入列中。
    此时我们将再次检查是否需要新增一个线程(上次检查后存在死亡的线程)
    或者进入此方法后,线程池关闭了。
    所以我们重新检查状态,如果线程池停止了就回滚队列,并且拒绝当前任务 ;如果没有线程就启动一个新的线程。

  3. 在以上的基础上,如果线程池满了,但线程数少于最大线程数,将创建新的线程;如果超出了最大线程数,将采取拒绝策略。

在这里插入图片描述

 	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);
    }

接下来就看下里面的一个重要方法addWorker,看下它的实现原理:

/**
* 检查是否可以根据当前池状态和给定的界限(核心或最大值)添加新worker。
* @param firstTask 待执行的人物
* @param core 是否处于核心线程池的数量下
* /
private boolean addWorker(Runnable firstTask, boolean core) {
    retry://循环标记
    for (;;) {
    	//获取当前线程池及运行状态
        int c = ctl.get();
        int rs = runStateOf(c);

        // 若线程池已关闭或者运行任务为空,直接返回false
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
           //当前线程数大于最大线程数时,返回false
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
              //线程数+1,通过CAS保证线程安全,此时退出循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
             //若以上的CAS操作+1没有成功,继续循环
            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);
        final Thread t = w.thread;
        if (t != null) {
        	//首先加锁,保证线程安全
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                //持有锁时将再次检查线程池的状态
                //为了预防ThreadFactory创建失败的情况
                //或者在锁获取到之前线程池已经关闭的情况
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) //预先检查线程t是否可启动
                        throw new IllegalThreadStateException();
                    //将当前任务worker加入工作组中
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

参考博客:
线程池的原理

猜你喜欢

转载自blog.csdn.net/sunjian1122/article/details/86564206
今日推荐