Java线程池(1)

来源:互联网 发布:淘宝运营工资6000高吗 编辑:程序博客网 时间:2024/05/23 02:06

昨天正好面试的时候,问了面试者线程池的问题,问题时这样的:”ThreadPoolExecutor中,假如corePoolSize=5, maximumPoolSize=10, workQueue的长度也是5,此时我向这个线程池中提交6个Runnable,线程池此时如何处理这第六个Runnable?”。

这道题目很多面试者容易犯错,因为直观上,我们感觉线程池应该这样处理:
(1) 当线程池中的线程数目小于maximumPoolSize时,新来一个任务的话,应该新创建一个线程来处理;
(2) 当线程池中存活的线程的数目达到了maximumPoolSize之后,新来的任务应该会被防到队列中,等在线程忙完之后再来处理。
实际情况是这样吗?先做一个实验:

    public class ThreadPoolTest {    public static void main(String[] args) throws Exception{        ExecutorService executorService = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));        List<Future> list = new ArrayList<Future>();        //创建5个长任务,每个长任务会运行1分钟左右的时候        int i = 5;        while (i-- > 0) {            list.add(executorService.submit(new LongRunnable()));        }        //提交一个短任务,段任务会打印它的运行时间        list.add(executorService.submit(new ShortRunnable()));        //打印当前提交时间        System.out.println("submit time:" + new Date());        //等待所有的任务执行完        for(Future future : list) {            future.get();        }        executorService.shutdown();    }    private static class ShortRunnable implements Runnable {        @Override        public void run() {            System.out.println("run time: " + new Date());        }    }    private static class LongRunnable implements Runnable {        @Override        public void run() {            int i = 60;            while (i-- > 0) {                try {                    Thread.sleep(1000L);                }catch (Exception e) {                    e.printStackTrace();                }            }        }    }

结果:

submit time:Tue Feb 23 15:05:28 CST 2016run time: Tue Feb 23 15:06:28 CST 2016

从结果中可以看出,第六个提交的任务的运行时间比他的提交时间大概晚了1分钟左右,也就是说,当我们提交第六个任务的时候,线程池并没有创建一个新的线程来运行它,而是等最开始创建的5个线程中有线程空闲了才来运行第六个任务。这时候我们提交的这个任务线程池把它放在哪的呢?应该是放在队列中的。

下面我们修改测试代码, 把list.add(executorService.submit(new ShortRunnable()));修改成

       i = 6;       while (i-- > 0) {           list.add(executorService.submit(new ShortRunnable()));       }

再运行下看看:
run time: Tue Feb 23 15:20:06 CST 2016
submit time:Tue Feb 23 15:20:06 CST 2016
run time: Tue Feb 23 15:20:06 CST 2016
run time: Tue Feb 23 15:20:06 CST 2016
run time: Tue Feb 23 15:20:06 CST 2016
run time: Tue Feb 23 15:20:06 CST 2016
run time: Tue Feb 23 15:20:06 CST 2016
此时我们发现, 提交的6个任务全部瞬间运行完了。这也说明了线程池的运行过程是这样的:
(1) 当提交一个任务交给线程池,而此时线程池中并没有空闲的线程来处理时, 线程池会把任务放在队列中缓存起来;
(2) 如果缓存队列满了,新提交一个任务时,此时线程池会判断池中的线程数目是否大于或等于maximumPoolSize, 如果没有大于或等于,则新创建一个线程来处理改任务。如果线程池中的线程数目已经大于或等于maximumPoolSize, 则拒绝服务。

我们看ThreadPoolExecutor的源代码:

    public void execute(Runnable command) {        if (command == null)            throw new NullPointerException();        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {            if (runState == RUNNING && workQueue.offer(command)) {                if (runState != RUNNING || poolSize == 0)                    ensureQueuedTaskHandled(command);            }            else if (!addIfUnderMaximumPoolSize(command))                reject(command); // is shutdown or saturated        }    }    private boolean addIfUnderCorePoolSize(Runnable firstTask) {            Thread t = null;            final ReentrantLock mainLock = this.mainLock;            mainLock.lock();            try {                if (poolSize < corePoolSize && runState == RUNNING)                    t = addThread(firstTask);            } finally {                mainLock.unlock();            }            if (t == null)                return false;            t.start();            return true;        }

从代码中可以看出,
(1) 当线程池中添加任务时,线程池首先会检查当前存活的线程是否已经达到了corePoolSize;
(2) 如果未达到,则创建一个新的线程来处理该任务;
(3) 如果存活线程数目已达到corePoolSize,或者创建新的执行线程失败,则执行runState == RUNNING && workQueue.offer(command)
把任务添加到队列中。
(4) 我们知道阻塞队列的offer方法是不阻塞的, 也就是当队列满了之后,workQueue.offer(command)会返回false,此时, 则添加线程来执行任务addIfUnderMaximumPoolSize(command);
(5) 如果addIfUnderMaximumPoolSize(command)返回false, 则说明线程池中存活的线程数目已经达到了最大的线程数目, 则拒绝执行。

1 0