深入理解Java线程池与Future模式

在人们的生活中,通常会合理安排时间,尽肯能的会用更少的时间做更多的事,人们管这种做法叫做统筹安排。

下面我们来看一个简单的例子:

例如:早上起床,想泡壶茶喝。

当时情况是:热水没有,水壶、茶壶、茶杯要洗,火已升了,茶叶也有,怎么办?

方法一:洗水壶,灌水,放在火上;等水烧开的时间去洗茶壶、茶杯,放茶叶;等水烧开了,泡茶喝。

方法二:洗水壶,洗茶壶,茶杯,放茶叶;灌水,放在火上;等水烧开了,泡茶喝。

方法三:洗水壶,灌水,放在火上;坐等水烧开了,洗茶壶、茶杯,放茶叶,泡茶喝。

显然,第一种方法更加节省时间,更加合乎统筹兼顾的思想,其实这个例子是华罗庚教授在《统筹方法》一文中运用的一个非常简单例子。

然而我们在开发的过程中,也会让程序在某一时刻同时运行多个任务,这里就会用到一个概念——线程

什么是线程

既然说到了线程,就不得不提到进程。

下面我来说一下我自己的理解,现如今所有的现代操作系统都通过进程和线程来支持并发。

进程是通常彼此独立运行的程序的实例,比如,如果你启动了一个Java程序,操作系统产生一个新的进程,与其他程序一起并行执行。在这些进程的内部,我们使用线程并发执行代码,这样一来,我们就可以最大限度的利用CPU可用的核心(core)。

生活场景映射程序编码

我再来举一个我们生活中的例子,假如朋友突然要到自己家里吃饭,然而我们又什么都没有准备,甚至连锅都没有。

那我们该如何去安排这件事呢。

场景1

网上购买锅比较方便,去超市买菜更放心,在快递员送锅的期间,我们肯定不会闲着,可以去超市买菜。

所以,我们需要在主线程里面另起一个子线程去执行网购的任务,但必须要等子线程执行完毕(锅送到了)之后我们才可以做饭,所以我们要拿到子线程提供的返回值。

先来看第一版代码:

package test;

import java.util.concurrent.TimeUnit;



/**
 * 线程池与Future模式
 * @author Evan
 * @date 2018年6月21日  
 * @version 1.0.0
 */
public class CookTest {

	//做饭
    static void doCook(Pot pot, Vegetables vt) {
    	if(pot != null) {
    		System.out.println("锅是好锅,没问题");
    		if(vt != null) {
        		System.out.println("蔬菜很新鲜,没问题");
        	}
    	}else {
    		System.out.println("锅还没到,做个屁");
    	}
    }
    //锅
    static class Pot {}
    //蔬菜
    static class Vegetables {}
	 // 网购线程
    static class getPot extends Thread{
        private Pot pot;
        @Override
        public void run() {
            System.out.println("买锅:执行下单");
            System.out.println("买锅:等待送货");
            try {
            	TimeUnit.SECONDS.sleep(1);  // 模拟送货时间:延迟1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("买锅:快递送到");
            pot = new Pot();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        /**
         * 网购
         */
        getPot thread = new getPot();
        thread.start();
        thread.join();//保证锅送到
        /**
         * 去超市买菜
         */
        TimeUnit.SECONDS.sleep(2);  // 模拟买菜时间:延迟2秒
        Vegetables vt = new Vegetables();
        System.out.println("买菜:菜已到位");
        /**
         * 做饭
         */
        System.out.println("做饭:是时候展现真正的技术啦");
        doCook(thread.pot, vt);
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
    
}

运行结果:

买锅:执行下单
买锅:等待送货
买锅:快递送到
买菜:菜已到位
做饭:是时候展现真正的技术啦
锅是好锅,没问题
蔬菜很新鲜,没问题
总共用时3016ms

从运行结果可以看出,虽然程序是以2个线程的方式去运行。但并不是我们预期想要的效果,我锅送来的期间,我们并没有同时在做其他的事,而是傻傻的等到锅送来之后,再去买菜,而对应的代码就是Thread线程调用Join方法对主线程进行了阻塞。

看到这里也许有人会问,线程不阻塞行不行。

我们尝试把thread.join()注释掉,再来看一下运行效果:

买锅:执行下单
买锅:等待送货
买锅:快递送到
买菜:菜已到位
做饭:是时候展现真正的技术啦
锅是好锅,没问题
蔬菜很新鲜,没问题
总共用时2000ms

运行结果依然没有问题,是不是略显尴尬?如果不找到问题所在,那上面的内容岂不是白写啦。

其实原因出在这里:

System.out.println("买锅:执行下单");
System.out.println("买锅:等待送货");
try {
     TimeUnit.SECONDS.sleep(1);  // 模拟送货时间:延迟1秒
} catch (InterruptedException e) {
     e.printStackTrace();
}
...
...
...



/**
 * 去超市买菜
 */
TimeUnit.SECONDS.sleep(2);  // 模拟买菜时间:延迟2秒

就是子线程的执行要比主线程快,因为这里我们使用了明确的时间数值去挂起线程,所以我们能很轻松的掌握主线程和子线程的运行时间,而在实际开发过程中,我们很难精准的控制一个线程的实际运行时间,因为他会受到很多可观因素的影响,比如网速,服务器配置,数据库访问速度等等,所以这种看似没有问题的问题,其实依然是一个需要解决的问题。如果把这两个数值对调一下。那么我们再来看一下运行效果:

买锅:执行下单
买锅:等待送货
买菜:菜已到位
做饭:是时候展现真正的技术啦
锅还没到,做个屁
总共用时1001ms
买锅:快递送到

很明显,这样一来,我们就没办法在调用doCook方法的时候拿到自己需要的子线程数据,因为子线程的run方法不执行完,属性pot就没有被实例化,还是null。

我们在使用线程的时候,大多数时候会选择继承Thread或是去实现Runnable,然而这两种方式的run方法是没有返回值的,如果要保存run方法里面计算的结果,拿到某些数据,就必须要等待run方法计算完成,无论计算过程耗时多久,我们也必须要等待。

面对这样的处境,我们就来引出本文的第一个概念,Future模式。

Future模式

对于多线程而言,如果线程A要等待线程B的结果,那么线程A没必要一直等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果时再取真实的结果值。

现在看来一下改过之后的代码

package test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;



/**
 * 线程池与Future模式
 * @author Evan
 * @date 2018年6月21日  
 * @version 1.0.0
 */
public class CookTest {

	//做饭
    static void doCook(Pot pot, Vegetables vt) {
    	if(pot != null) {
    		System.out.println("锅是好锅,没问题");
    		if(vt != null) {
        		System.out.println("蔬菜很新鲜,没问题");
        	}
    	}else {
    		System.out.println("锅还没到,做个屁");
    	}
    }
    //锅
    static class Pot {}
    //蔬菜
    static class Vegetables {}

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        
        /**
         * 网购
         */
        Callable getPot = new Callable() {
            @Override
            public Pot call() throws Exception {
            	System.out.println("买锅:执行下单");
                System.out.println("买锅:等待送货");
                TimeUnit.SECONDS.sleep(2);  // 模拟送货时间:延迟2秒
                System.out.println("买锅:快递送到");
                return new Pot();
            }
            
        };
        FutureTask task = new FutureTask(getPot);
        new Thread(task).start();
        /**
         * 去超市买菜
         */
        TimeUnit.SECONDS.sleep(1);  // 模拟买菜时间:延迟1秒
        Vegetables vt = new Vegetables();
        System.out.println("买菜:菜已到位");
        /**
         * 做饭
         */
        if (!task.isDone()) {  // 询问快递员,是否到货
            System.out.println("做饭:锅还没到(根据自己的需要,可以调用cancel方法,直接取消订单)");
        }
        Pot pot = task.get();
        System.out.println("做饭:是时候展现真正的技术啦");
        doCook(pot, vt);
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
    
}

运行结果:

买锅:执行下单
买锅:等待送货
买菜:菜已到位
做饭:锅还没到(根据自己的需要,可以调用cancel方法,直接取消订单)
买锅:快递送到
做饭:是时候展现真正的技术啦
锅是好锅,没问题
蔬菜很新鲜,没问题
总共用时2030ms

从运行结果可以看出,使用了Future模式之后,得到了我们想要的效果,在子线程异步执行的同时,主线程没有阻塞,继续执行自己的逻辑,同时可以通过FutureTask去控制子线程(获取结果、取消子线程计算)

我们来简单分析一下上面的代码

1、我们实例化了Callable接口,则必须实现他的call方法,那么我们先来看一下Callable和Runnable的区别

@FunctionalInterface
public interface Callable {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}




@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface Runnable is used
     * to create a thread, starting the thread causes the object's
     * run method to be called in that separately executing
     * thread.
     * 

* The general contract of the method run is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }

其实这两个接口很相似,在我看来,Callable是Runnable的补充,Callable是一个泛型接口,并且他的call方法可以把任何对象当作返回值返回。然后我们继续往下看。

2、我们实例化了一个FutureTask,然后把刚刚实例化的Callable以参数的方式传到FutureTask的构造方法中去。

   /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Callable}.
     *
     * @param  callable the callable task
     * @throws NullPointerException if the callable is null
     */
    public FutureTask(Callable callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

那么我们来看一下这个类的层级关系

public class FutureTask implements RunnableFuture
public interface RunnableFuture extends Runnable, Future

这样看起来,好似绕了一圈又回来了,FutureTask是RunnableFuture泛型接口的实现类,而RunnableFuture接口又继承了Runnable和Future,其实看名字也能清楚,RunnableFuture是Runnable和Future的结合体,而FutureTask又是RunnableFuture的具体实现,那这样一来,FutureTask就同时拥有了Future的实现和Runnable的实现。

下面我们来看一下FutureTask的实现方法:

V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。

V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。

boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true

boolean isCancelled() :如果任务完成前被取消,则返回true

boolean cancel(boolean mayInterruptIfRunning)

a.如果任务还没开始,执行cancel(...)方法将返回false

b.如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true

c.当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false

d.当任务已经完成,执行cancel(...)方法将返回false

mayInterruptRunning参数表示是否中断执行中的线程。

void run():如果任务还未执行,执行callable.call方法,然后调用set方法设置call方法的返回结果以及任务的状态

通过方法分析我们也知道实际上Future提供了3种功能:

a.能够中断执行中的任务

b.判断任务是否执行完成

c.获取任务执行完成后额结果。

看到这里也许有的童鞋会问到,上面的代码实例中为什么不直接用task.run(),而是以task为参数新实例了一个Thread线程呢。

请看调试效果:

task.run()

深入理解Java线程池与Future模式_第1张图片

 

 

 

 

 

 

 

 

买锅:执行下单
买锅:等待送货
买锅:快递送到
买菜:菜已到位
做饭:是时候展现真正的技术啦
锅是好锅,没问题
蔬菜很新鲜,没问题
总共用时3005ms

new Thread(task).start();

深入理解Java线程池与Future模式_第2张图片

 

 

 

 

 

 

因为如果在主线程中直接使用FutureTask的实例task去调用run方法的话,其实就是在当前线程中运行run方法,然后再去调用callable.call,当全部执行完call方法之后,线程才会继续,这样一来所有的操作都是在同一线程内完成的,根本没有去异步执行。

3、继续上面的代码,我们往下看,后面我调用了isDone方法查看执行状态,在上文中我提到了isDone方法的解释,然后直接调用task.get方法获取到返回值。

其实经过源码分析,如果task还未执行完毕,则调用get方法的线程会进入等待队列等待,因为在get方法内会调用awaitDone,awaitDone本身是一个死循环,它直到task的状态变为已完成状态或者等待时间超过超时时间或者线程中断才会跳出循环,然而为了节省开销,线程不会一直等待,而是会阻塞,使用LockSupport的park系列方法实现线程阻塞,有兴趣的同学可以自行查看FutureTask的源码。

其实对比第一版代码,我们节省了1秒,而这1秒的来源就是节省在了用等待送锅的时间去买菜(子线程异步执行的同时主线程继续执行自身的业务

通过上面的场景分析,我们就完成了对Java原生Future模式最基本的了解。

既然Future模式的作用点在于多线程,那么一个优秀的多线程开发必然少不了线程池的支持。

我们再来介入一个场景。

场景2

准备请朋友吃饭,这次不来家里吃了,我们去找个饭馆消费,当我们到了饭馆,点了好多菜品之后,这个菜单会给到后厨,由厨师来完成客人点的菜品。从做菜到上菜的过程,就是一个线程完成一个任务的过程。

假如你是一个饭馆老板,你会每当有客人点一道菜品,会新请一个厨师吗?答案必然是不会,这样会严重浪费开销,如果客人不多,点菜不多,那老板只是亏本,如果客人多了菜品也多了,那饭馆直接会面临倒闭。

对于系统来讲也是一样的。Java线程的创建与销毁需要一定的开销,每当一个新的任务需要被执行的时候,如果我们每次都新建一个线程去执行的话,那么这些线程的创建与销毁将消耗大量的计算资源,所以这种策略可能会使处于高负荷状态的应用最终崩溃。

如果使用场景2的映射关系的话:

菜品=等待被执行的任务

厨师=线程

厨房=线程池

当我们第一次创建线程池的时候会生成一个任务队列,然后第一次执行execute()或者submit()方法时会创建一个循环的线程,用于反复读取队列中的任务并执行他,但是在第一次提交的任务是不用进入任务队列的,而是由刚创建的线程直接执行,而后续的execute()或者 submit()操作则直接提交Runnable任务到队列里,当队列为空时,循环线程就会被阻塞。

下面我们来看一下四种最常用的创建线程池的方法:

Executors.newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
Executors.newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
Executors.newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
Executors.newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

以上四种方式都可以选择是否携带ThreadFactory做为参数创建。如果不携带ThreadFactory做为参数,那么Executors会使用默认的线程工厂:DefaultThreadFactory,DefaultThreadFactory是ThreadFactory接口的默认实现。

 

newFixedThreadPool

本文主要以newFixedThreadPool为例,去深入了解一下线程池构造及运行方式

我们可以通过调用Executors类的static newFixedThreadPool()方法获得一个固定线程池,这个线程池内会有固定数量的线程去读取同一个任务队列,但这些线程不是同时产生的,而是每当有一个新的任务提交【即执行一次execute()或者submit()】就会产生一个,当提交的任务数量超过线程池线程的固定数量,而超出的任务直接会被提交到任务队列中等待被执行,然后再由这固定数量线程中的某个线程去获取并执行该任务,当线程池已经创建了固定数量的线程后,他们不会被回收,更不会再继续增加。

调用语法:

ExecutorService fixedPool = Executors.newFixedThreadPool(2);

源码:

(1)创建newFixedThreadPool的方法:

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
 }



public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue(),
                                      threadFactory);
}

上面这两个方法是创建固定数量的线程池的两种方法,两者的区别是:第二种创建方法多了一个线程工厂的方法。

(2)ThreadPoolExecutor的构造函数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
}




public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue 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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

ThreadPollExecutor中的所有的构造函数最终都会调用上面这个构造函数,接下来我们来分析一下这些参数的含义:

corePoolSize:

线程池启动后,在池中保持的线程的最小数量。需要说明的是线程数量是逐步到达corePoolSize值的。例如corePoolSize被设置为10,而任务数量只有5,则线程池中最多会启动5个线程,而不是一次性地启动10个线程。

maxinumPoolSize:

线程池中能容纳的最大线程数量,如果超出,则使用RejectedExecutionHandler拒绝策略处理。

keepAliveTime:

线程的最大生命周期。这里的生命周期有两个约束条件:

一:该参数针对的是超过corePoolSize数量的线程;

二:处于非运行状态的线程。举个例子:如果corePoolSize(最小线程数)为10,maxinumPoolSize(最大线程数)为20,而此时线程池中有15个线程在运行,过了一段时间后,其中有3个线程处于等待状态的时间超过keepAliveTime指定的时间,则结束这3个线程,此时线程池中则还有12个线程正在运行。

unit:

这是keepAliveTime的时间单位,可以是纳秒,毫秒,秒,分钟等。

workQueue:

任务队列。当线程池中的线程都处于运行状态,而此时任务数量继续增加,则需要一个容器来容纳这些任务,这就是任务队列。这个任务队列是一个阻塞式的单端队列。

threadFactory:

定义如何启动一个线程,可以设置线程的名称,并且可以确定是否是后台线程等。

handler:

拒绝任务处理器。由于超出线程数量和队列容量而对继续增加的任务进行处理的程序。

然后我们通过以上的参数,再详细解释一下线程池的管理过程:

首先创建一个线程池,然后根据任务的数量逐步将线程增大到corePoolSize,如果此时仍有任务增加,则放置到workQueue中,直到workQueue爆满为止,然后继续增加池中的线程数量(增强处理能力),最终达到maxinumPoolSize。那如果此时还有任务要增加进来呢?这就需要handler来处理了,或者丢弃新任务,或者拒绝新任务,或者挤占已有的任务。在任务队列和线程池都饱和的情况下,一旦有线程处于等待(任务处理完毕,没有新任务)状态的时间超过keepAliveTime,则该线程终止,也就是说池中的线程数量会逐渐降低,直至为corePoolSize数量为止。

接下来我们来看一下如何提交任务至任务队列:

java.util.concurrent.AbstractExecutorService这个类的submit方法

Submit方法

   /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public Future submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public  Future submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public  Future submit(Callable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

这是三个重载方法,分别对应Runnable、带结果的Runnable接口和Callable回调函数。这三个方法的返回都与Future相关,也就是说,我们可以通过Submit方法将线程池与Future模式完美结合在一起,可以使我们利用Future更好的控制线程池的运行线程的状态及返回结果。其中的newTaskFor也是一个重载的方法,它通过层层的包装,把Runnable接口包装成了适配RunnableFuture的实现类,来看一下具体实现:

protected  RunnableFuture newTaskFor(Runnable runnable, T value) {
        return new FutureTask(runnable, value);
}
public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
}
 public static  Callable callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter(task, result);
 }

继续前面的Submit方法,他们最终调用的却都是execute方法,而execute方法才是这里的重中之重,也是我们分析的重点

execute方法

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();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        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);
}

我们可以看到,在源码中会有注释此方法执行的3个步骤,我们结合自己的理解以及翻译。大致可以明白

1、如果少于corePoolSize数量的线程在运行,则启动一个新的线程并把传进来的Runnable做为第一个任务。然后会检查线程的运行状态和worker的数量,阻止不符合要求的任务添加到线程中

2、如果一个任务成功的放入到了队列中,我们仍然需要二次检查我们是否应该添加线程或者停止。因此我们重新检查线程状态,是否需要回滚队列,或者是停止或者是启动一个新的线程

3、如果我们不能添加队列任务了,但是仍然在往队列中添加任务,如果添加失败的话,用拒绝策略来处理。

在这里最主要的是addWorker这个方法:

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

            // Check if queue empty only if necessary.
            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;
                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 {
                    // 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);
                        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;
   }

我们在这个方法里创建一个线程,注意这个线程不是我们的任务线程,而是经过包装的Worker线程。所以这里的run方法是Worker这个类中的run方法。execute方法是通过Worker类启动的一个工作线程,执行的是我们的第一个任务,然后该线程通过getTask方法从任务队列总获取任务,之后再继续执行。这个任务队列是一个BlockingQueue,是一个阻塞式的,也就是说如果该队列元素为0,则保持等待状态。直到有任务进入为止。

 

好啦。本文的文章到此就先结束啦。我们大致能明白Future模式的概念及工作原理,以及深入了解了Java线程池中newFixedThreadPool线程池

管理线程的管理过程。我们还可以通过submit方法将线程池与Future模式结合在一起。

本人才疏学浅,如果文章有理解错误的地方,还请指正。感谢!

转载于:https://my.oschina.net/u/3179885/blog/1836991

你可能感兴趣的:(深入理解Java线程池与Future模式)