线程池中execute与submit的区别说明

 更新时间:2022年03月23日 14:54:20   作者:每年进步一点点  
这篇文章主要介绍了线程池execute与submit的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

线程池execute与submit区别

在使用线程池的时候,看到execute()与submit()方法。都可以使用线程池执行一个任务,但是两者有什么区别呢?

execute

void execute(Runnable command);

submit

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

相同点:

1 都可以执行任务

2 参数都支持runnable

不同点:

1 submit支持接收返回值 详见例1。

2 execute 任务里面的异常必须捕获,不能向上抛出;submit支持的Callable支持向上抛出异常,需要由返回值.get()来进行接收。详见例2。

例1

public class ExecutorTest { 
    public static ExecutorService executorService = Executors.newFixedThreadPool(3); 
    public static void main(String[] args) {
        Future<?> result1 = executorService.submit(new Callable() {
            @Override
            public Object call() throws Exception {
                TimeUnit.SECONDS.sleep(10);
                System.out.println("Thread1 sleep 10 seconds");
                return true;
            }
        });
        Future<?> result2 = executorService.submit(new Callable() {
            @Override
            public Object call() throws Exception {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("Thread2 sleep 5 seconds");
                return false;
            }
        });
        try {
            System.out.println("Thread1 return:"+result1.get());
            System.out.println("Thread2 return:"+result2.get());
            System.out.println("finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } 
    }
}

该例子的返回结果:

Thread2 sleep 5 seconds
Thread1 sleep 10 seconds
Thread1 return:true
Thread2 return:false
finished

解释:可以看到接收到了两个线程的返回结果。利用thread.sleep来模拟耗时操作,直到两个线程执行完毕之后,才会输出finished。利用Callable的返回阻塞,来等待这n个线程的执行完毕,然后将这n个线程的结果响应回去;其执行时间,基本上取决于最耗时的那个线程。

适用场景:在某些情况下,需要获得一组线程的结果,给调用端。

例2

而execute不支持向上抛出异常,必须将异常捕获。

线程池submit和execute方法原理

线程池的作用

1. 避免大量的线程强占资源

2. 避免大量的线程创建和销毁带来的开销  

线程池的原理

创建线程池的时候,可以使用executors的静态方法,也可以使用new ThreadPoolExecutor的方式手动创建线程池,通过在线程池中指定参数达到创建不同类型的线程池的效果

其中,executors底层其实也是调用的new ThreadPoolExecutor()的方式创建的,是对不同线程池的封装,

线程的执行有两种方式,一种是submit(runnable v)的形式,一种是execute(runnable b) 的形式,不同的是submit可以返回一个future的实现类,相同的一点是submit底层其实也是调用的execute

调用execut方法,首先判断传入的参数是否为空,如果为空,抛出异常,如果不为空,使用获取ctl值,计算出当前线程状态码,通过状态码计算出当前线程池工作线程是否小于核心线程数量

如果小于,判断添加工作线程操作是否正常,如果正常,直接返回,如果不正常,继续执行获取ctl值,在添加工作线程的过程中,首先通过循环的方式保证ctl在加1的情况下状态同步,如果不同步,一直循环到同步为止,添加完成后,创建线程工作对象,把工作线程添加到set集合中,并执行.start,如果执行不成功,从set中删除添加的worker对象,并且ctl回滚到之前没有自增的值.

如果上述中添加工作线程失败,或者当前线程池中工作线程数量操作和信息数量,执行下列逻辑

判断当前线程池状态是否是running状态:

如果不是running状态,或者是running状态,并且添加到线程队列失败,重新添加个工作线程,此时入参中第二个参数用于添加工作线程的逻辑中当前工作线程数量与最大线程数量做对比,如果添加失败,执行reject处理类处理

如果是running状态,并且添加队列成功,重新获取ctl值,判断当前线程池状态如果是不是running状态,并且从对象中删除成功,则当前线程交给拒绝线程处理器处理,如果不满足上面条件,判断当前线程池的工作线程数如果为0,重新添加一个不带任务的线程. 

//AbstractExecutorService.java文件
    // executorService 中的 submit 方法
    public Future<?> submit(Runnable task) {
        // 首先判断传入的runnable 对象是否为空
        if (task == null) throw new NullPointerException();
        // 创建一个 futuretask 对象
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
    // 根据runnable 创建一个futuretask对象
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
    // ThreadPoolExecutor.java文件
    // 执行创建线程池的方法
    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);
    }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java 数据结构进阶二叉树题集下

    Java 数据结构进阶二叉树题集下

    二叉树可以简单理解为对于一个节点来说,最多拥有一个上级节点,同时最多具备左右两个下级节点的数据结构。本文将带你通过实际题目来熟练掌握
    2022-04-04
  • 扩展tk.mybatis的流式查询功能实现

    扩展tk.mybatis的流式查询功能实现

    mybatis查询默认是一次获取全部,如果数据过于庞大,就会导致OOM问题,本文就介绍了tk.mybatis 流式查询,具有一定的参考价值,感兴趣的可以了解一下
    2021-12-12
  • 关于java中线程安全问题详解

    关于java中线程安全问题详解

    最近工作中遇到不少多线程问题,但自己一直对多线程的理解比较表层,所以深入研究了一番,下面这篇文章主要给大家介绍了关于java中线程安全问题的相关资料,需要的朋友可以参考下
    2021-11-11
  • java 使用Graphics2D在图片上写字

    java 使用Graphics2D在图片上写字

    这篇文章主要介绍了java 使用Graphics2D在图片上写字,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Springboot jdbctemplate整合实现步骤解析

    Springboot jdbctemplate整合实现步骤解析

    这篇文章主要介绍了Springboot jdbctemplate整合实现步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Java中实现文件上传下载的三种解决方案(推荐)

    Java中实现文件上传下载的三种解决方案(推荐)

    这篇文章主要介绍了Java中实现文件上传下载的三种解决方案的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-07-07
  • servlet之ServletContext简介_动力节点Java学院整理

    servlet之ServletContext简介_动力节点Java学院整理

    这篇文章主要介绍了servlet之ServletContext简介,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • spring集成okhttp3的步骤详解

    spring集成okhttp3的步骤详解

    okhttp是一个封装URL,比HttpClient更友好易用的工具,下面这篇文章主要给大家介绍了关于spring集成okhttp3的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2018-04-04
  • Spring Boot启动过程(六)之内嵌Tomcat中StandardHost、StandardContext和StandardWrapper的启动教程详解

    Spring Boot启动过程(六)之内嵌Tomcat中StandardHost、StandardContext和Sta

    这篇文章主要介绍了Spring Boot启动过程(六)之内嵌Tomcat中StandardHost、StandardContext和StandardWrapper的启动教程详解,需要的朋友可以参考下
    2017-04-04
  • Java常用API类之Math System tostring用法详解

    Java常用API类之Math System tostring用法详解

    System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包,Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数,toString() 方法用于返回以一个字符串表示的 Number 对象值
    2021-10-10

最新评论