线程池 ThreadPoolExecutor

ThreadPoolExecutor构造方法

转载 Java基础“线程池”-你想知道的都在这里

在这里插入图片描述

线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> 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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这里是7个参数(我们在开发中用的更多的是5个参数的构造方法),OK,那我们来看看这里七个参数的含义:

参数含义
corePoolSize线程池中核心线程的数量
maximumPoolSize线程池中最大线程数量
keepAliveTime非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长
unit第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等
workQueue线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory为线程池提供创建新线程的功能,这个我们一般使用默认即可
handler拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。

通过构造方法可以看出:

maximumPoolSize < corePoolSize的时候会抛出异常;

maximumPoolSize(最大线程数) = corePoolSize(核心线程数) + noCorePoolSize(非核心线程数);

当currentSize<corePoolSize时,没什么好说的,直接启动一个核心线程并执行任务。

当currentSize>=corePoolSize、并且workQueue未满时,添加进来的任务会被安排到workQueue中等待执行。当workQueue已满,但是currentSize<maximumPoolSize时,会立即开启一个非核心线程来执行任务。此时就会开启线程的活性检查,对于设置为keepAliveTime时间没有活动的线程将会被回收。

当currentSize>=corePoolSize、workQueue已满、并且currentSize>maximumPoolSize时,调用handler默认抛出RejectExecutionExpection异常。

2.线程池执行流程

在这里插入图片描述

一个线程从被提交(submit)到执行共经历以下流程:

  1. 线程池判断核心线程池里是的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程

  2. 线程池判断工作队列是否已满。如果工作队列没有满,则将新提交的任务储存在这个工作队列里。如果工作队列满了,则进入下一个流程。

  3. 线程池判断其内部线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已满了,则交给饱和策略来处理这个任务。

线程池在执行execute方法时
在这里插入图片描述
主要有以下四种情况

  1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(需要获得全局锁)
  2. 如果运行的线程等于或多于corePoolSize ,则将任务加入BlockingQueue
  3. 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(需要获得全局锁)
  4. 如果创建新线程将使当前运行的线程超出maxiumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

线程池采取上述的流程进行设计是为了减少获取全局锁的次数。在线程池完成预热(当前运行的线程数大于或等于corePoolSize)之后,几乎所有的excute方法调用都执行步骤2。

ThreadPoolExecutor类中execute()方法:

//ThreadPoolExecutor.execute()
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        // 活动线程数 < corePoolSize
        if (workerCountOf(c) < corePoolSize) {
            // 直接启动新的线程。第二个参数true:addWorker中会重新检查workerCount是否小于corePoolSize
            if (addWorker(command, true))
                // 添加成功返回return;
                c = ctl.get();
        }

        // 活动线程数 >= corePoolSize
        // runState为RUNNING && 队列未满
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();

            // double check
            // 非RUNNING状态 则从workQueue中移除任务并拒绝
            if (!isRunning(recheck) && remove(command))
                reject(command);// 采用线程池指定的策略拒绝任务

                // 线程池处于RUNNING状态 || 线程池处于非RUNNING状态但是任务移除失败
            else if (workerCountOf(recheck) == 0)
                // 这行代码是为了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。
                // 添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务
                addWorker(null, false);

            // 两种情况:
            // 1.非RUNNING状态拒绝新的任务
            // 2.队列满了启动新的线程失败(workCount > maximumPoolSize)
        } else if (!addWorker(command, false))
            reject(command);
    }

ctl变量定义:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

这个变量使用来干嘛的呢?它的作用有点类似我们在《7.ReadWriteLock接口及其实现ReentrantReadWriteLock》中提到的读写锁有读、写两个同步状态,而AQS则只提供了state一个int型变量,此时将state高16位表示为读状态,低16位表示为写状态。这里的clt同样也是,它表示了两个概念:
workerCount:当前有效的线程数
runState:当前线程池的五种状态,Running、Shutdown、Stop、Tidying、Terminate。
  int型变量一共有32位,线程池的五种状态runState至少需要3位来表示,故workCount只能有29位,所以代码中规定线程池的有效线程数最多为2^29 -1。

//ThreadPoolExecutor
private static final int COUNT_BITS = Integer.SIZE – 3;     //32-3=29,线程数量所占位数
private static final int CAPACITY = (1 << COUNT_BITS) – 1;    //低29位表示最大线程数,2^29-1
//五种线程池状态
private static final int RUNNING = -1 << COUNT_BITS;    /int型变量高3位(含符号位)101表RUNING
private static final int SHUTDOWN = 0 << COUNT_BITS;    //高3位000
private static final int STOP = 1 << COUNT_BITS;    //高3位001
private static final int TIDYING = 2 << COUNT_BITS;    //高3位010
private static final int TERMINATED = 3 << COUNT_BITS;    //高3位011

在这里插入图片描述
生命周期转换如下:
在这里插入图片描述

3.常见的线程池

Executors工厂类可以创建四种类型的线程池,通过Executors.newXXX即可创建。

1.FixedThreadPool

创建一个固定数量线程的线程池。

// Executors#newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}

可以看到newFixedThreadPool中调用的是ThreadPoolExecutor类,传递的参数corePoolSize= maximumPoolSize=nThread。回顾线程池的执行原理,当一个任务提交到线程池中,首先判断核心线程池里有没有空闲线程,有则创建线程,没有则将任务放到任务队列(这里是有界阻塞队列LinkedBlockingQueue(长度Integer.MAX_VALUE))中,如果任务队列已经满了的话,对于newFixedThreadPool来说,它的最大线程池数量=核心线程池数量,此时任务队列也满了,将不能扩展创建新的线程来执行任务。

2.SingleThreadExecutor

创建只包含一个线程的线程池。

//Executors# newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegateExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}

只有一个线程的线程池好像有点奇怪,并且并没有直接将返回ThreadPoolExecutor,甚至也没有直接将线程池数量1传递给newFixedThreadPool返回。那就说明这个只含有一个线程的线程池,或许并没有只包含一个线程那么简单。在其源码注释中这么写到:创建只有一个工作线程的线程池用于操作一个无界队列(如果由于前驱节点的执行被终止结束了,一个新的线程将会继续执行后继节点线程)任务得以继续执行,不同于newFixedThreadPool(1)不会有额外的线程来重新继续执行后继节点。也就是说newSingleThreadExecutor自始至终都只有一个线程在执行,这和newFixedThreadPool一样,但如果线程终止结束过后newSingleThreadExecutor则会重新创建一个新的线程来继续执行任务队列中的线程,而newFixedThreaPool则不会。

3.CachedThreadPool

根据需要创建新线程的线程池。

//Executors#newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
  return new ThreadPooExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}

可以看到newCachedThread返回的是ThreadPoolExecutor,其参数核心线程池corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE,这也就是说当任务被提交到newCachedThread线程池时,将会直接把任务放到SynchronousQueue任务队列中,maximumPool从任务队列中获取任务。注意SynchronousQueue是一个没有容量的队列,也就是说每个入队操作必须等待另一个线程的对应出队操作,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,newCachedThreadPool会不断创建线程,线程多并不是一件好事,严重会耗尽CPU和内存资源。

4. ScheduledThreadPool

它用来处理延时任务或定时任务。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

它接收SchduledFutureTask类型的任务,有两种提交任务的方式:

  1. scheduledAtFixedRate
  2. scheduledWithFixedDelay

SchduledFutureTask接收的参数:
time:任务开始的时间
sequenceNumber:任务的序号
period:任务执行的时间间隔

它采用DelayQueue存储等待的任务
DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber排序;
DelayQueue也是一个无界队列;
工作线程的执行过程:

工作线程会从DelayQueue取已经到期的任务去执行;

执行结束后重新设置任务的到期时间,再次放回DelayQueue

public class MyThreadextends Thread{
    private String name;

    public MyThread(String name){
        this.name = name;
    }

    @Override
    public void run(){
        super.run();
        System.out.println(new Date().toString()+" "+Thread.currentThread().getName()+" "+name);
    }
}
Thread thread1 = new MyThread("MyThread1");
Thread thread2 = new MyThread("MyThread2");
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(2);
exec.scheduleAtFixedRate(thread1, 0, 3000, TimeUnit.MILLISECONDS);
exec.scheduleAtFixedRate(thread2, 0, 2000, TimeUnit.MILLISECONDS);
运行结果:

Wed Mar 13 15:44:07 CST 2019 pool-1-thread-2 MyThread2
Wed Mar 13 15:44:07 CST 2019 pool-1-thread-1 MyThread1
Wed Mar 13 15:44:09 CST 2019 pool-1-thread-2 MyThread2
Wed Mar 13 15:44:10 CST 2019 pool-1-thread-1 MyThread1
Wed Mar 13 15:44:11 CST 2019 pool-1-thread-2 MyThread2
Wed Mar 13 15:44:13 CST 2019 pool-1-thread-1 MyThread1
Wed Mar 13 15:44:13 CST 2019 pool-1-thread-2 MyThread2
Wed Mar 13 15:44:15 CST 2019 pool-1-thread-2 MyThread2
Wed Mar 13 15:44:16 CST 2019 pool-1-thread-1 MyThread1
Wed Mar 13 15:44:17 CST 2019 pool-1-thread-2 MyThread2

题外话:newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool,这三者都直接或间接调用了ThreadPoolExecutor,为什么它们三者没有直接是其子类,而是通过Executors来实例化呢?这是所采用的静态工厂方法,在java.util.Connections接口中同样也是采用的静态工厂方法来创建相关的类。这样有很多好处,静态工厂方法是用来产生对象的,产生什么对象没关系,只要返回原返回类型或原返回类型的子类型都可以,降低API数目和使用难度,在《Effective Java》中的第1条就是静态工厂方法。

4.Java中的阻塞队列

由于上面的构造方法涉及到了阻塞队列,所以补充一些阻塞队列的知识。

阻塞队列:我的理解是,生产者——消费者,生产者往队列里放元素,消费者取,如果队列里没有元素,消费者线程取则阻塞,如果队列里元素满了,则生产者线程阻塞。

常见的阻塞队列有下列7种:

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的无界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

a. 直接提交的队列:SynchronousQueue对象提供
SynchronousQueue没有容量,每一个插入操作都需要等待一个相应的删除操作,反之,每一个删除操作都要等待对应的插入操作。如果使用SynchronousQueue,提交的任务不会被真实地保存,而总是将新任务提交给线程执行。如果没有空闲的线程,则尝试创建新的线程,如果线程数量已经达到最大值,则执行拒绝策略。
b.有界的任务队列:可以使用ArrayBlockingQueue
当使用有界的任务队列时,若有新的任务需要执行,如果线程池的实际线程数小于corePoolSize,则会优先创建新的线程,若大于corePoolSize,则会将新任务加入到等待队列。若等待队列已满,无法加入,则在总线程数不大于maximumPoolSize的前提下,创建新的进程执行任务。若大于maximumPoolSize,则执行拒绝策略。可见,除非系统非常繁忙,否则确保核心线程数维持在corePoolSize。
c.无界的任务队列:可通过LinkedBlockingQueue实现
除非系统资源耗尽,否则无界的的任务队列不存在任务入队失败的情况。当有新的任务到来,系统的线程数小于corePoolSize时,线程池会生成新的线程执行任务,但当系统的线程数达到corePoolSize后,就不会继续增加。若后续仍有新的新的任务加入,而又没有空闲的线程资源,则任务直接进入队列等待。
d.优先任务队列:带有执行优先级的队列,通过PriorityBlockingQueue实现。可以控制任务的执行先后顺序,是一个特殊的无界队列,可以根据任务自身的优先级顺序先后执行,在确保系统性能的同时,也能有很好的质量保证(总是确保高优先级的任务先执行)

5.RejectedExecutionHandler:饱和策略

当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:

当工作队列满了,不同策略的处理方式为:

1.Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常 可由 调用者捕获。

2.CallerRuns策略:为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用exector的线程中运行新的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

3.Discard策略:新提交的任务被抛弃。

4.DiscardOldest策略:队列的是“队头”的任务,然后尝试提交新的任务。(不适合工作队列为优先队列场景)

6.扩展线程池

自定义线程创建ThreadFactory

ThreadFactory是一个接口,只有一个方法newThread(Runnable r),用来创建线程。

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

自定义线程,可以跟踪线程池究竟在何时创建了多少线程,可以自定义线程的名称、组以及优先级等信息,可以将所有的线程设置为守护线程。

我们自己写线程池工厂类

class CustomerThreadFactory implements ThreadFactory{

    private String name;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    CustomerThreadFactory(String name){
        this.name = name;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r,name+threadNumber.getAndIncrement());
        return thread;
    }
}

只需要在进行线程池实例化的时候将此工厂类加上去即可

public static void customerThread(){
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0,Integer.MAX_VALUE,1, TimeUnit.SECONDS,new SynchronousQueue<>(),
                new CustomerThreadFactory("customerThread"));

        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.printf(Thread.currentThread().getName());
                    System.out.printf("\n");
                }
            });
        }
    }

接下来我们执行此语句,发现每个线程的名字已经变了

customerThread1
customerThread10
customerThread9
customerThread8
customerThread7
customerThread6
customerThread5
customerThread4
customerThread3
customerThread2

提供了beforeExecute()、 afterExecute()和terminated()方法

protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }

ThreadPoolExecutor是个可扩展的线程池,提供了beforeExecute()、 afterExecute()和terminated()三个接口对线程池进行控制,可进行扩展来实现对线程池运行状态的追踪,输出一些有用的调试信息,以帮助系统故障诊断。

关闭线程池

调用shutdown()关闭线程池,是个比较安全的方法,如果当前正有线程在执行,shutdown()方法并不会立即暴力地终止所有任务,他会等待所有任务执行完成后,再关闭线程池,但它并不会等待所有线程执行完成后再返回,因此,可以简单地理解成shutdown()只是发送了一个信号而已。但在shutdown()方法执行后,这个线程池就不能再接受其他新的任务了。

问:shutdown()有什么功能?
答:阻止新来的任务提交,对已经提交了的任务不会产生任何影响。当已经提交的任务执行完后,它会将那些闲置的线程(idleWorks)进行中断,这个过程是异步的。

问:如何阻止新来的任务提交?
答:通过将线程池的状态改成SHUTDOWN,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的。

问:为何对提交的任务不产生任何影响?
答:在调用中断任务的方法时,它会检测workers中的任务,如果worker对应的任务没有中断,并且是空闲线程,它才会去中断。另外的话,workQueue中的值,还是按照一定的逻辑顺序不断的往works中进行输送的,这样一来,就可以保证提交的任务按照线程本身的逻辑执行,不受到影响。

shutdownNow()
问:shutdownNow()有什么功能?
答:阻止新来的任务提交,同时会中断当前正在运行的线程,即workers中的线程。另外它还将workQueue中的任务给移除,并将这些任务添加到列表中进行返回。

问:如何阻止新来的任务提交?
答:通过将线程池的状态改成STOP,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的.

问:如果我提交的任务代码块中,正在等待某个资源,而这个资源没到,但此时执行shutdownNow(),会出现什么情况?

答:当执行shutdownNow()方法时,如遇已经激活的任务,并且处于阻塞状态时,shutdownNow()会执行1次中断阻塞的操作,此时对应的线程报InterruptedException,如果后续还要等待某个资源,则按正常逻辑等待某个资源的到达。例如,一个线程正在sleep状态中,此时执行shutdownNow(),它向该线程发起interrupt()请求,而sleep()方法遇到有interrupt()请求时,会抛出InterruptedException(),并继续往下执行。在这里要提醒注意的是,在激活的任务中,如果有多个sleep(),该方法只会中断第一个sleep(),而后面的仍然按照正常的执行逻辑进行。

线程池线程数量

理解是服务器是CPU密集型还是IO密集型能够帮助我们更好的设置线程池中的参数。

  • IO密集型:大部分时间CPU闲着,在等待磁盘的IO操作
  • CPU(计算)密集型:大部分时间磁盘IO闲着,等着CPU的计算操作

CPU密集型任务:说明包含了大量的运算操作,比如有N个CPU,那么就配置线程池的容量大小为N+1,这样能获得最优的利用率。因为CPU密集型的线程恰好在某时因为发生一个页错误或者因为其他的原因而暂停,刚好有一个额外的线程,可以确保在这种情况下CPU周期不会中断工作。

IO密集任务:说明CPU大部分时间都是在等待IO的阻塞操作,那么此时就可以将线程池的容量大小配置的大一些。此时可以根据一些参数进行计算大概你的线程池的数量多少合适。
最优的线程池大小=CPU的数量 * 目标CPU的使用率(大于等于0,小于1) * (1 + 等待时间与计算时间的比率)
在java中,可通过Runtime.getRuntime().availableProcessors()取得可用的cpu数量
线程池与工作队列优雅地使用线程池

页缺失(英语:Page fault,又名硬错误、硬中断、分页错误、寻页缺失、缺页中断、页故障等)指的是当软件试图访问已映射在虚拟地址空间中,但是当前并未被加载在物理内存中的一个分页时,由中央处理器的内存管理单元所发出的中断.

7.使用Executors 创建线程池的弊端

  1. FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
  2. CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

8. submit方法

import java.util.concurrent.*;

/**
 * ThreadPoolExecutor#sumit(Callable<T> task)
 * Created by yulinfeng on 6/17/17.
 */
public class Sumit1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> callable = new Callable<String>() {
            public String call() throws Exception {
                System.out.println("This is ThreadPoolExetor#submit(Callable<T> task) method.");
                return "result";
            }
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(callable);
        System.out.println(future.get());
    }
}

转载: ThreadPoolExecutor线程池之submit方法

对于线程来讲,如果不需要它返回结果则实现Runnable,而如果需要执行结果的话则可以实现Callable。在线程池同样execute提供一个不需要返回结果的任务执行,而对于需要结果返回的则可调用其submit方法。

在Executor接口中只定义了execute方法,而submit方法则是在ExecutorService接口中定义的。

//ExecutorService
public interface ExecutorService extends Executor {
  ...
  <T> Future<T> submit(Callable<T> task);
  <T> Future<T> submit(Runnable task, T result);
  <T> Future<T> submit(Runnable task);
  ...
}

而在其子类AbstractExecutorService实现了submit方法。

//AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService {
  ...
  public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
  }
  public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
  }
  public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerExeption();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask; 
  }
  ...
}

在AbstractExecutorService实现的submit方法实际上是一个模板方法,定义了submit方法的算法骨架,其execute交给了子类。
尽管submit方法能提供线程执行的返回值,但只有实现了Callable才会有返回值,而实现Runnable的线程则是没有返回值的,也就是说在上面的3个方法中,submit(Callable task)能获取到它的返回值,submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值或者准确来说交给线程处理一下,而最后一个方法submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。

从上面的源码可以看到,这三者方法几乎是一样的,关键就在于

RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);

将一个任务作为参数传递给了newTaskFor,然后调用execute方法,最后进而返回ftask

//AbstractExecutorService#newTaskFor
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
  return new FutureTask<T>(callable);
}
  protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
  return new FutureTask<T>(runnable, value);
}

返回了一个FutureTask实例,FutureTask实现了Future和Runnable接口。Future接口是Java线程Future模式的实现,可以用来异步计算,实现Runnable接口表示可以作为一个线程执行。FutureTask实现了这两个接口意味着它代表异步计算的结果,同时可以作为一个线程交给Executor来执行。

execute(ftask);会调用FutureTask中run()方法,进而调用Callable接口中的call()方法

   public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

//调用future.get()方法,获得返回值
  public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);  //等待,
        return report(s);
    }
 private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

引用:
Java基础“线程池”-你想知道的都在这里
13.ThreadPoolExecutor线程池之submit方法
Java并发包源码学习之线程池(一)ThreadPoolExecutor源码分析
如何优雅的使用线程池
Java线程池实现原理及其在美团业务中的实践

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值