java并发编程-线程池解析(非常详细)

在前面的文章中介绍过如何去创建一个线程,这个比较简单,那么会有个问题,如果创建的线程多了,会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间

那么如何让现有的线程复用呢?那就是通过线程池来达到这个效果!

首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然后再讲述它的实现原理,接着给出了它的使用示例,最后讨论了一下如何合理配置线程池的大小。

一、ThreadPoolExecutor类

ThreadPoolExecutor是java.uitl.concurrent(简称JUC java并发工具包)下面的类,是线程池中最核心的一个类。

在ThreadPoolExecutor类中提供了四个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

1、参数的含义

1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

5、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

一般队列有以下几种选择:

ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

6、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

7、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

拒绝策略一般有以下几种,当然也可以自定义拒绝策略(实现RejectedExecutionHandler接口):

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

2、结构解析

ThreadPoolExecutor继承AbstractExecutorService

AbstractExecutorService实现ExecutorService接口

ExecutorService继承Executor

public interface Executor {
    void execute(Runnable command);
}
public interface ExecutorService extends Executor {
 
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
 
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

  submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果

二、线程池实现原理图

1、流程图

2、执行图 

三、线程池的具体实现原理

1.线程池的生命周期

在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个生命周期的阶段:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
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;
private static final int TERMINATED = 3 << COUNT_BITS;
private static int ctlOf(int rs, int wc) { return rs | wc; }

runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;

  • RUNNING
    这是线程池的初始状态。此状态下线程池会接受新任务并且处理队列中等待的任务。
  • SHUTDOWN
    RUNNING状态下调用shutdown方法后进入此状态。此状态下线程池不接受新任务,但会处理队列中等待的任务。
  • STOP
    RUNNING/SHUTDOWN状态下调用shutdownNow方法后进入此状态。此状态下线程池不接受新任务,也不处理既有等待任务,并且会中断既有运行中的线程。
  • TIDYING
    SHUTDOWN/STOP状态会流转到此状态。此时所有任务都已运行完毕,工作线程数为0,任务队列都为空。从字面角度理解,此时线程池已经清干净了。
  • TERMINATED
    TIDYING状态下,线程池执行完terminated钩子方法后进入此状态,此时线程池已完全终止。

2、ThreadPoolExecutor的成员变量

private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小
                                                              //、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
 
private volatile long  keepAliveTime;    //线程存货时间   
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
 
private volatile int   poolSize;       //线程池中当前的线程数
 
private volatile RejectedExecutionHandler handler; //任务拒绝策略
 
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
 
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
 
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

3、execute()方法

在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可:

/**
 * execute方法可以说是线程池中最核心的方法,
 * 在继承链上层的AbstractExecutorService中将各种接受新任务的方法最终转发给了此方法进行任务处理。
 */
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * 分类讨论:
     * 1. 如果当前线程数<核心线程数,则会开启一个新线程来执行提交的任务。
     *
     * 2. 尝试向任务队列中添加任务。这时需要再次检查方法开始到当前时刻这段间隙,
     *    线程池是否已经关闭了/线程池中没有工作线程了。
     *    如果线程池已经关闭了,需要在任务队列中移除先前提交的任务。
     *    如果没有工作线程了,则需要添加一个空任务工作线程用于执行提交的任务。
     *
     * 3. 如果无法向阻塞队列中添加任务,则尝试创建一个新的线程执行任务。
     *    如果失败,回调饱和策略处理任务。
     */
    int c = ctl.get();
    // 线程数 < corePoolSize
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 检查线程池是否处于运行状态,并向任务队列中添加任务
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        /*
         * 再次检查是否线程池处于运行状态,如果不是则移除任务并回调饱和策略拒绝任务。
         * 因为有可能上面if条件读到线程池处于运行状态,而后shutdown/shutdownNow方法被调用,
         * 这时候需要把尝试刚才加入任务队列中的任务移除。
         */
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果workerCount为0,需要添加一个工作线程用于执行提交的任务
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    /*
     *  添加一个新的工作线程处理任务。
     *  如果失败,则说明线程池已经关闭或者已经饱和了,此时回调饱和策略来拒绝任务。
     */
    else if (!addWorker(command, false))
        reject(command);
}

4、addWorker方法

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        /*
         * 如果线程池状态至少为STOP,返回false,不接受任务。
         * 如果线程池状态为SHUTDOWN,并且firstTask不为null或者任务队列为空,同样不接受任务。
         */
        if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                    firstTask == null &&
                    ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            /*
             * CAPACITY为(1<<29)-1,这是线程池中线程数真正的上界,绝不允许超过。
             * 因为ThreadPoolExecutor设计中是用低29位表示工作线程数的。
             *
             * 否则根据参数中是否以corePoolSize为上界进行判断,如果超过,则新增worker失败。
             */
            if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 成功新增workCount,跳出整个循环往下走。
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();
            /* 
             * 重读总控状态,如果运行状态变了,重试整个大循环。
             * 否则说明是workCount发生了变化,重试内层循环。
             */
            if (runStateOf(c) != rs)
                continue retry;
        }
    }

    // 运行到此处时,线程池线程数已经成功+1,下面进行实质操作。

    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 {
                // 由于获取锁之前线程池状态可能发生了变化,这里需要重新读一次状态。
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 向工作线程集合添加新worker,更新largestPoolSize。
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 成功增加worker后,启动该worker线程。
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // worker线程如果没有成功启动,回滚worker集合和worker计数器的变化。
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

5、addWorkerFailed

在新增工作线程失败的情况下,调用addWorkerFailed:

  1. 从worker集合删除失败的worker。
  2. workCount减1。
  3. 调用tryTerminate尝试终止线程池。
private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

6、Worker类的实现

private final class Worker implements Runnable {
    private final ReentrantLock runLock = new ReentrantLock();
    private Runnable firstTask;
    volatile long completedTasks;
    Thread thread;
    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
    }
    boolean isActive() {
        return runLock.isLocked();
    }
    void interruptIfIdle() {
        final ReentrantLock runLock = this.runLock;
        if (runLock.tryLock()) {
            try {
        if (thread != Thread.currentThread())
        thread.interrupt();
            } finally {
                runLock.unlock();
            }
        }
    }
    void interruptNow() {
        thread.interrupt();
    }
 
   /**
 * 工作线程运行核心逻辑。
 * 简单来说做的事情就是不断从任务队列中拿取任务运行。
 */
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    // 把firstTask设置为null,从GC角度来看,这处代码很重要。
    w.firstTask = null;
    // 置互斥锁状态为0,此时可以被中断。
    w.unlock();
    // 用于标记完成任务时是否有异常。
    boolean completedAbruptly = true;
    try {
        // 循环:初始任务(首次)或者从阻塞阻塞队列里拿一个(后续)。        
        while (task != null || (task = getTask()) != null) {
           /*
            * 获取互斥锁。
            * 在持有互斥锁时,调用线程池shutdown方法不会中断该线程。
            * 但是shutdownNow方法无视互斥锁,会中断所有线程。
            */
            w.lock();
            /*
             * 这里if做的事情就是判断是否需要中断当前线程。
             * 如果线程池至少处于STOP阶段,当前线程未中断,则中断当前线程;
             * 否则清除线程中断位。
             *
             * if条件中的Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP)
             * 做的事情说穿了就是清除中断位并确认目前线程池状态没有达到STOP阶段。
             */  
            if ((runStateAtLeast(ctl.get(), STOP) ||
                        (Thread.interrupted() &&
                         runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                wt.interrupt();
            try {
                // 调用由子类实现的前置处理钩子。
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 真正的执行任务
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    // 调用由子类实现的后置处理钩子。
                    afterExecute(task, thrown);
                }
            } finally {
                // 清空task, 计数器+1, 释放互斥锁。
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        /*
         * 处理工作线程退出。
         * 上面主循环中的前置处理、任务调用、后置处理都是可能会抛出异常的。
         */
        processWorkerExit(w, completedAbruptly);
    }
}
 
    public void run() {
        try {
            Runnable task = firstTask;
            firstTask = null;
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);   //当任务队列中没有任务时,进行清理工作       
        }
    }
}

 它实际上实现了Runnable接口,因此上面的Thread t = threadFactory.newThread(w);效果跟下面这句的效果基本一样:

	Thread t = new Thread(w);

相当于传进去了一个Runnable任务,在线程t中执行这个Runnable。

既然Worker实现了Runnable接口,那么自然最核心的方法便是run()方法了

public void run() {
    try {
        Runnable task = firstTask;
        firstTask = null;
        while (task != null || (task = getTask()) != null) {
            runTask(task);
            task = null;
        }
    } finally {
        workerDone(this);
    }
}

从run方法的实现可以看出,它首先执行的是通过构造器传进来的任务firstTask,在调用runTask()执行完firstTask之后,在while循环里面不断通过getTask()去取新的任务来执行,那么去哪里取呢?自然是从任务缓存队列里面去取,getTask是ThreadPoolExecutor类中的方法,并不是Worker类中的方法,下面是getTask方法的实现:

/**
 * 工作线程从任务队列中拿取任务的核心方法。
 * 根据配置决定采用阻塞或是时限获取。
 * 在以下几种情况会返回null从而接下来线程会退出(runWorker方法循环结束):
 * 1. 当前工作线程数超过了maximumPoolSize(由于maximumPoolSize可以动态调整,这是可能的)。
 * 2. 线程池状态为STOP (因为STOP状态不处理任务队列中的任务了)。
 * 3. 线程池状态为SHUTDOWN,任务队列为空 (因为SHUTDOWN状态仍然需要处理等待中任务)。
 * 4. 根据线程池参数状态以及线程是否空闲超过keepAliveTime决定是否退出当前工作线程。
 */
private Runnable getTask() {
    // 上次从任务队列poll任务是否超时。
    boolean timedOut = false;

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        /*
         * 如果线程池状态已经不是RUNNING状态了,则设置ctl的工作线程数-1
         * if条件等价于 rs >= STOP || (rs == SHUTDOWN && workQueue.isEmpty())
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        /*
         * allowCoreThreadTimeOut是用于设置核心线程是否受keepAliveTime影响。
         * 在allowCoreThreadTimeOut为true或者工作线程数>corePoolSize情况下,
         * 当前工作线程受keepAliveTime影响。
         */
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        /*
         * 1. 工作线程数>maximumPoolSize,当前工作线程需要退出。
         * 2. timed && timedOut == true说明当前线程受keepAliveTime影响且上次获取任务超时。
         *    这种情况下只要当前线程不是最后一个工作线程或者任务队列为空,则可以退出。
         *
         *    换句话说就是,如果队列不为空,则当前线程不能是最后一个工作线程,
         *    否则退出了就没线程处理任务了。
         */ 
        if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
            // 设置ctl的workCount减1, CAS失败则需要重试(因为上面if中的条件可能不满足了)。
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 根据timed变量的值决定是时限获取或是阻塞获取任务队列中的任务。
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            // workQueue.take是不会返回null的,因此说明poll超时了。
            timedOut = true;
        } catch (InterruptedException retry) {
            // 在阻塞队列上等待时如果被中断,则清除超时标识重试一次循环。
            timedOut = false;
        }
    }
}

7、processWorkerExit方法

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    /*
     * 因为正常退出,workerCount减1这件事情是在getTask拿不到任务的情况下做掉的。
     * 所以在有异常的情况下,需要在本方法里给workCount减1。
     */
    if (completedAbruptly)
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 累加completedTaskCount,从工作线程集合移除自己。
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    // 由于workCount减1,需要调用tryTerminate方法。
    tryTerminate();

    int c = ctl.get();
    // 只要线程池还没达到STOP状态,任务队列中的任务仍然是需要处理的。
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            /* 
             * 确定在RUNNING或SHUTDOWN状态下最少需要的工作线程数。
             *
             * 默认情况下,核心线程不受限制时影响,
             * 在这种情况下核心线程数量应当是稳定的。
             * 否则允许线程池中无线程。
             */
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果任务队列非空,至少需要1个工作线程。
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 无需补偿工作线程。
            if (workerCountOf(c) >= min)
                return;
        }
        // 异常退出或者需要补偿一个线程的情况下,加一个空任务工作线程。
        addWorker(null, false);
    }
}

8、线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

9、线程池容量的动态调整 

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

  • setCorePoolSize:设置核心池大小
  • setMaximumPoolSize:设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

 10、任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

 四、示例

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

public class ThreadPoolExecutorTest {
	
	@Test
	public void threadPoolExecutorTest(){
         ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 100, TimeUnit.MILLISECONDS,
                 new ArrayBlockingQueue<Runnable>(5));
          
         for(int i=0;i<10;i++){
             TestTask testTask = new TestTask(i);
             executor.execute(testTask);
             System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
             executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
         }
         executor.shutdown();
     }
		 
		 
		
	class TestTask implements Runnable {
	    private int taskNum;
	     
	    public TestTask(int num) {
	        this.taskNum = num;
	    }
	     
	    @Override
	    public void run() {
	        System.out.println("正在执行task "+taskNum);
	        try {
	            Thread.currentThread().sleep(4000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        System.out.println("task "+taskNum+"执行完毕");
	    }
	}
}

测试结果:

线程池中线程数目:1,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:1,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:2,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:3,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:4,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:4,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task 0
正在执行task 1
正在执行task 2
正在执行task 8

从执行结果可以看出,当线程池中线程的数目大于3时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。 

不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:

Executors.newCachedThreadPool();        //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor();   //创建容量为1的缓冲池
Executors.newFixedThreadPool(int);    //创建固定容量大小的缓冲池

 下面是这三个静态方法的具体实现;

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

  实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

有个问题就是如果超大量的请求的话,上面三个方法就不适用了,前面两个队列是无界队列,一直往里面加会撑爆内存,最后这个因为是无限制增加线程也会耗尽CPU

五、如何合理配置线程池的大小

1)、CPU密集型

这种任务我们要尽量使用较小的线程池,一般是Cpu核心数+1

因为CPU密集型任务CPU的使用率很高,若开过多的线程,只能增加线程上下文的切换次数,带来额外的开销

2)、IO密集型

方法一:可以使用较大的线程池,一般CPU核心数 * 2

IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别的任务,充分利用cpu时间

方法二:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wwwzhouzy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值