线程池之execute()和submit()区别源码分析

一、execute()的具体实现

execute方法是Executor接口里面的方法,不是ExecutorService接口里的方法,如果如果使用execute方法的话,其实是调用其父类的方法。

 // 执行创建线程池的方法
    public void execute(Runnable command) {
    
    
        // 首先判断传入的线程是否为空
        if (command == null)
            // 为空,抛出异常
            throw new NullPointerException();
        // 获取线程池的状态码, 这个状态码是自增的,原子类型的自增, 在执行addworker后ctl会加1
        int c = ctl.get();
        // 通过状态码,获取线程池中的线程的数量,如果小于核心数量
        if (workerCountOf(c) < corePoolSize) {
    
    
            // 添加线程到线程池,并且为true时使用核心线程数作为边界,如果false ,使用最大数量线程数作为边界
            if (addWorker(command, true))
                // 添加完成后,返回
                return;
            // 如果添加失败,重新获取状态值
            c = ctl.get();
        }
        // 执行下面逻辑有两种情况
        //      1. 工作线程数大于核心线程
        //      2. 添加线程时出错
        // 如果线程池中线程的数量大于核心的数量, 判断如果是运行状态, 并且也把线程加进了阻塞队列 workQueue 中
        if (isRunning(c) && workQueue.offer(command)) {
    
    
            // 重新获取 线程池 状态值
            int recheck = ctl.get();
            // 判断当前线程池如果不是运行状态,并且成功从队列中移除(从workQueue中移除线程, 并尝试终止线程池)
            if (! isRunning(recheck) && remove(command))
                // 执行拒绝执行线程的处理
                reject(command);
                // 如果工作线程数为0
            else if (workerCountOf(recheck) == 0)
                // 添加一个null的工作包装对象
                addWorker(null, false);

        } else if (!addWorker(command, false))
            // 如果添加到线程池中出错,执行拒接的线程
            reject(command);
    }

    // 创建一个原子类对象用于计算线程的中状态
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // integer.size 为 32
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // 即高3位为1,低29位为0,该状态的线程池会接收新任务,也会处理在阻塞队列中等待处理的任务
    private static final int RUNNING    = -1 << COUNT_BITS;
    // 即高3位为0,低29位为0,该状态的线程池不会再接收新任务,但还会处理已经提交到阻塞队列中等待处理的任务
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    // 即高3位为001,低29位为0,该状态的线程池不会再接收新任务,不会处理在阻塞队列中等待的任务,而且还会中断正在运行的任务
    private static final int STOP       =  1 << COUNT_BITS;
    // 即高3位为010,低29位为0,所有任务都被终止了,workerCount为0,为此状态时还将调用terminated()方法
    private static final int TIDYING    =  2 << COUNT_BITS;
    // 即高3位为100,低29位为0,terminated()方法调用完成后变成此状态  
    private static final int TERMINATED =  3 << COUNT_BITS;

    // 用户计算线程的状态 32位中 高3位为1 低29位为0 
    private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
    // 用于计算线程池中线程的数量 32位中 高3位为0  低29位为1
    private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
    // rs 为 runState, wc 为 workerCount 通过工作状态和线程数量来计算出 ctl
    private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }

    // 添加工作线程的方法
    private boolean addWorker(Runnable firstTask, boolean core) {
    
    
        // 设置循环跳出点,如果执行到某个位置,使用break,直接跳出的是这个标签范围内的所有循环
        retry:
        for (;;) {
    
    
            // 获取线程状态
            int c = ctl.get();
            int rs = runStateOf(c);
            // 判断线程池状态是否在shutdown上以及 状态不是关闭并且添加的线程不为空,并且线程队列中的线程不是空的
            if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
                // 如果满足上面条件,说明线程池已经不适合添加新的线程了, 直接返回false
                return false;
            // 如果不满足上面条件,说明线程池可以添加线程, 下面这个循环主要是对ctl进行操作,保证在增1后线程状态保持同步
            for (;;) {
    
    
                // 获取工作线程数量
                int wc = workerCountOf(c);
                // 判断当前线程池中工作线程数量是否大于线程容量,大于核心线程数或最大线程数
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    // 满足条件,说明当前线程不是适合添加新的线程的
                    return false;
                // 如果工作数量少于最大量或者核心线程数或最大线程数, 工作线程数加1,即操作ctl,通过cas的方式
                if (compareAndIncrementWorkerCount(c))
                    // 如果添加成功,跳出内循环,
                    break retry;
                // 如果添加失败,重新获取ctl
                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);
            // 创建一个worker 工作线程
            final Thread t = w.thread;
            // 判断创建的线程是否为空
            if (t != null) {
    
    
                 // 如果不为空,获取锁对象
                final ReentrantLock mainLock = this.mainLock;
                // 开始加锁
                mainLock.lock();
                try {
    
    
                    // 获取线程池状态
                    int rs = runStateOf(ctl.get());
                    // 如果线程池状态是running或者线程池状态关闭并且传入的线程是空的
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
    
    
                        // 判断创建的工作线程是否是活动状态(已经开始还没有死掉)
                        if (t.isAlive()) // precheck that t is startable
                            // 如果是活动状态,抛出 非法线程状态异常 
                            throw new IllegalThreadStateException();
                        // 如果不是活动状态, 添加到set集合中,这个set集合只有持有mainlock才可以访问
                        workers.add(w);
                        // 获取集合长度
                        int s = workers.size();
                        // 如果存放刚才创建的workers工作线程的集合中的线程数超过最大的池的大小
                        if (s > largestPoolSize)
                            // 把set集合中的数量代替原线程池最大值
                            largestPoolSize = s;

                        workerAdded = true;
                    }
                } finally {
    
    
                    // 释放锁
                    mainLock.unlock();
                }
                // 根据前面的判断是否需要开启线程,如果线程已经是活动的,不需要开启,如果不是活动线程,开启线程
                if (workerAdded) {
    
    
                    t.start();
                    // 开启成功,设置workerStarted 为 true
                    workerStarted = true;
                }
            }
        } finally {
    
    
            // 如果工作线程开启失败,调用添加到失败的线程中
            if (! workerStarted)
                // 从set中移除失败的线程,并且ctl减1, 并且尝试终止线程池
                addWorkerFailed(w);
        }
        return workerStarted;
    }

    // 线程开启失败后的方法
    private void addWorkerFailed(Worker w) {
    
    
        // 获取锁
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
    
    
            if (w != null)
                // 如果线程不为空,从set集合中移除没有开启成功的线程
                workers.remove(w);
            // 减去之前ctl增加的1
            decrementWorkerCount();
            // 尝试中断线程
            tryTerminate();
        } finally {
    
    
            mainLock.unlock();
        }
    }

    // 通过cas方式ctl加1
    private boolean compareAndIncrementWorkerCount(int expect) {
    
    
        return ctl.compareAndSet(expect, expect + 1);
    }

    // 移除线程
    public boolean remove(Runnable task) {
    
    
        // 从等待队列中一尺线程
        boolean removed = workQueue.remove(task);
        // 尝试终止线程池
        tryTerminate(); // In case SHUTDOWN and now empty
        return removed;
    }

    // 使用拒绝处理对象执行拒接指定线程
    final void reject(Runnable command) {
    
    
        handler.rejectedExecution(command, this);
    }

  • if (workerCountOf© < corePoolSize) 判断当前活跃线程数是否小于corePoolSize(核心线程数),如果小于,则调用addWorker创建线程执行任务
  • 如果不小于corePoolSize,则将任务添加到workQueue队列
  • 如果放入workQueue失败,则创建线程执行任务,如果这时创建线程失败(当前线程数不小于maximumPoolSize时),就会调用reject(内部调用handler)拒绝接受任务。

二、submit()方法

ExecutorService里重载里三个submit方法

 <T> Future<T> submit(Callable<T> task);
 <T> Future<T> submit(Runnable task, T result);
  Future<?> submit(Runnable task);

submit方法:首先我们知道Callable是有返回值的,Runnable是没有返回值的,但是我们一般使用线程池都会想要知道这个线程的执行情况,Callbale使用线程池结合Future可以获取线程运行的情况,其实线程池使用Runnable也是可以有返回值的

submit 方式使用 Callable 入参时的具体实现

返回值是一个泛型,形参是实现了Callable的类,我们来看一下 他具体的方法,具体的方法在AbstractExecutorService类里面,AbstractExecutorService类是ExecutorService的实现类。

 public FutureTask(Callable<V> callable) {
    
    
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    //重写run方法
    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);
        }
    }

submit 方式使用 Runnable 入参时的具体实现

static final class RunnableAdapter implements Callable {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}

总结:

  • 根据源码可以看到 execute 仅可以接受Runnable类型,而 submit 重载了三个方法,参数可以是 Runnable类型、Runnable 类型+泛型T 、Callable 类型接口。

  • 从上面源码可以看出 submit 方法实际上如果用Runnable类型的接口可以有返回值,也可以没有返回值。

  • 传递Runnable类型接口加泛型T会被进一步封装,在 Executors 这个类里面有个内部类 RunnableAdapter 实现了 Callable 接口。

  • 看submit方法可以看出,submit最终也是在调用 execute 方法,无论是 Runnable 还是 Callable类型接口,都会被封装成 FutureTask 继续执行。

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

  • 如果使用submit方法提交,会进一步封装成FutureTask,执行execute方法,在FutureTask里面重写的run方法里面调用
    Callable 接口的call方法。

    扫描二维码关注公众号,回复: 12562260 查看本文章

小程序演示submit方法

好文章:https://blog.csdn.net/qq_25806863/article/details/71214033

public class RunnableTestMain {
    
    

    public static void main(String[] args) {
    
    
        ExecutorService pool = Executors.newFixedThreadPool(2);
        
        /**
         * execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。
         */
        pool.execute(new RunnableTest("Task1")); 
        
        /**
         * submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。请看下面:
         */
        Future future = pool.submit(new RunnableTest("Task2"));
        
        try {
    
    
            if(future.get()==null){
    
    //如果Future's get返回null,任务完成
                System.out.println("任务完成");
            }
        } catch (InterruptedException e) {
    
    
        } catch (ExecutionException e) {
    
    
            //否则我们可以看看任务失败的原因是什么
            System.out.println(e.getCause().getMessage());
        }
    }
}


public class RunnableTest implements Runnable {
    
    
    
    private String taskName;
    
    public RunnableTest(final String taskName) {
    
    
        this.taskName = taskName;
    }

    @Override
    public void run() {
    
    
        System.out.println("Inside "+taskName);
        throw new RuntimeException("RuntimeException from inside " + taskName);
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_42754971/article/details/113805590