为什么引入Executor线程池框架

new Thread()的缺点

每次new Thread()耗费性能

调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。

不利于扩展,比如如定时执行、定期执行、线程中断

采用线程池的优点

重用存在的线程,减少对象创建、消亡的开销,性能佳

可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞

提供定时执行、定期执行、单线程、并发数控制等功能

Executor的介绍

在Java 5之后,并发编程引入了一堆新的启动、调度和管理线程的API。

Executor框架便是Java 5中引入的,

其内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中。

Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

Executors方法介绍

Executors工厂类

通过Executors提供四种线程池,newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool。

1.public static ExecutorService newFixedThreadPool(int nThreads)

创建固定数目线程的线程池。

2.public static ExecutorService newCachedThreadPool()

创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线 程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

3.public static ExecutorService newSingleThreadExecutor()

创建一个单线程化的Executor。

4.public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

1.newFixedThreadPool创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

示例
 ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

运行结果:总共只会创建5个线程, 开始执行五个线程,当五个线程都处于活动状态,再次提交的任务都会加入队列等到其他线程运行结束,当线程处于空闲状态时会被下一个任务复用

2.newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程

示例:

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

运行结果:可以看出缓存线程池大小是不定值,可以需要创建不同数量的线程,在使用缓存型池时,先查看池中有没有以前创建的线程,如果有,就复用.如果没有,就新建新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

3.newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行

schedule(Runnable command,long delay, TimeUnit unit)创建并执行在给定延迟后启用的一次性操作

示例:表示从提交任务开始计时,5000毫秒后执行
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 20; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.schedule(syncRunnable, 5000, TimeUnit.MILLISECONDS);
        }

运行结果和newFixedThreadPool类似,不同的是newScheduledThreadPool是延时一定时间之后才执行

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnitunit)

创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        Runnable syncRunnable = new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, Thread.currentThread().getName());
            }
        };
        executorService.scheduleAtFixedRate(syncRunnable, 5000, 3000, TimeUnit.MILLISECONDS);

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟

 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        Runnable syncRunnable = new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        executorService.scheduleWithFixedDelay(syncRunnable, 5000, 3000, TimeUnit.MILLISECONDS);

4.newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 20; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

运行结果:只会创建一个线程,当上一个执行完之后才会执行第二个

ExecutorService

ExecutorService是一个接口,ExecutorService接口继承了Executor接口,定义了一些生命周期的方法。

public interface ExecutorService extends Executor {

    void shutdown();//顺次地关闭ExecutorService,停止接收新的任务,等待所有已经提交的任务执行完毕之后,关闭ExecutorService

    List<Runnable> shutdownNow();//阻止等待任务启动并试图停止当前正在执行的任务,停止接收新的任务,返回处于等待的任务列表

    boolean isShutdown();//判断线程池是否已经关闭

    boolean isTerminated();//如果关闭后所有任务都已完成,则返回 true。注意,除非首先调用 shutdown 或 shutdownNow,否则 isTerminated 永不为 true。

    boolean awaitTermination(long timeout, TimeUnit unit)//等待(阻塞)直到关闭或最长等待时间或发生中断,timeout - 最长等待时间 ,unit - timeout 参数的时间单位  如果此执行程序终止,则返回 true;如果终止前超时期满,则返回 false 

    <T> Future<T> submit(Callable<T> task);//提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。该 Future 的 get 方法在成功完成时将会返回该任务的结果。

    <T> Future<T> submit(Runnable task, T result);//提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功完成时将会返回给定的结果。

    Future<?> submit(Runnable task);//提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功 完成时将会返回 null

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)//执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。返回列表的所有元素的 Future.isDone() 为 true。
        throws InterruptedException;

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)//执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。返回列表的所有元素的 Future.isDone() 为 true。
        throws InterruptedException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks)//执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。一旦正常或异常返回后,则取消尚未完成的任务。
        throws InterruptedException, ExecutionException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。

ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

Executor执行Runnable任务

一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上执行。下面是是Executor执行Runnable任务的示例代码:

public class TestCachedThreadPool{
    public static void main(String[] args){
        ExecutorService executorService = Executors.newCachedThreadPool();
//      ExecutorService executorService = Executors.newFixedThreadPool(5);
//      ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++){
            executorService.execute(new TestRunnable());
            System.out.println("************* a" + i + " *************");
        }
        executorService.shutdown();
    }
}   

class TestRunnable implements Runnable{
    public void run(){
        System.out.println(Thread.currentThread().getName() + "线程被调用了。");
    }
}  

结果

Executor执行Callable任务

在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future。

下面给出一个Executor执行Callable任务的示例代码:

public class CallableDemo{
    public static void main(String[] args){
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<String>> resultList = new ArrayList<Future<String>>();   

        //创建10个任务并执行
        for (int i = 0; i < 10; i++){
            //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
            Future<String> future = executorService.submit(new TaskWithResult(i));
            //将任务执行结果存储到List中
            resultList.add(future);
        }   

        //遍历任务的结果
        for (Future<String> fs : resultList){
                try{
                    while(!fs.isDone);//Future返回如果没有完成,则一直循环等待,直到Future返回完成
                    System.out.println(fs.get());     //打印各个线程(任务)执行的结果
                }catch(InterruptedException e){
                    e.printStackTrace();
                }catch(ExecutionException e){
                    e.printStackTrace();
                }finally{
                    //启动一次顺序关闭,执行以前提交的任务,但不接受新任务
                    executorService.shutdown();
                }
        }
    }
}   

class TaskWithResult implements Callable<String>{
    private int id;   

    public TaskWithResult(int id){
        this.id = id;
    }   

    /**
     * 任务的具体过程,一旦任务传给ExecutorService的submit方法,
     * 则该方法自动在一个线程上执行
     */
    public String call() throws Exception {
        System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());
        //该返回结果将被Future的get方法得到
        return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();
    }
}  

某次执行结果如下:

从结果中可以同样可以看出,submit也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回。

自定义线程池

自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池,用该类很容易实现自定义的线程池,这里先贴上示例程序:

public class ThreadPoolTest{
    public static void main(String[] args){
        //创建等待队列
        BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);
        //创建线程池,池中保存的线程数为3,允许的最大线程数为5
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue);
        //创建七个任务
        Runnable t1 = new MyThread();
        Runnable t2 = new MyThread();
        Runnable t3 = new MyThread();
        Runnable t4 = new MyThread();
        Runnable t5 = new MyThread();
        Runnable t6 = new MyThread();
        Runnable t7 = new MyThread();
        //每个任务会在一个线程上执行
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        pool.execute(t6);
        pool.execute(t7);
        //关闭线程池
        pool.shutdown();
    }
}   

class MyThread implements Runnable{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + "正在执行。。。");
        try{
            Thread.sleep(100);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}  

参考

http://gold.xitu.io/entry/57cbaf667db2a2007895256e

http://blog.csdn.net/ns_code/article/details/17465497

http://www.infoq.com/cn/articles/executor-framework-thread-pool-task-execution-part-01

http://www.cnblogs.com/limingluzhu/p/4858776.html

我的微信二维码如下,欢迎交流讨论

欢迎关注《IT面试题汇总》微信订阅号。每天推送经典面试题和面试心得技巧

微信订阅号二维码如下:

Java程序员必备知识-多线程框架Executor详解的更多相关文章

  1. Java程序员必备后台前端框架--Layui【从入门到实战】(一)

    layui入门使用及图标的使用 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] [编程工具:IDEA] 下载Layui与文件分析 下载直接去官网下载即可 文件分析 下载完成后,解压会 ...

  2. Java程序员必备后台前端框架--Layui【从入门到实战】(二)

    layui使用 导航菜单.选项卡 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] [编程工具:IDEA] 导航菜单 在layui官网中示例中,我们可以找到关于导航的例子: 我们点击查 ...

  3. Java多线程框架Executor详解

       原文链接  http://www.imooc.com/article/14377 为什么引入Executor线程池框架new Thread()的缺点 每次new Thread()耗费性能调用ne ...

  4. Java 程序员必备的 15 个框架,前 3 个地位无可动摇!

    Java 程序员方向太多,且不说移动开发.大数据.区块链.人工智能这些,大部分 Java 程序员都是 Java Web/后端开发.那作为一名 Java Web 开发程序员必须需要熟悉哪些框架呢? 今天 ...

  5. Java程序员必备的一些流程图

    Java程序员必备的一些流程图 转自https://juejin.im/post/5d214639e51d4550bf1ae8df 前言: 整理了一些Java基础流程图/架构图,做一下笔记,大家一起学 ...

  6. Java程序员必备的 15框开发工具

    15款Java程序员必备的开发工具 如果你是一名Web开发人员,那么用膝盖想也知道你的职业生涯大部分将使用Java而度过.这是一款商业级的编程语言,我们没有办法不接触它. 对于Java,有两种截然不同 ...

  7. Android java程序员必备技能,集合与数组中遍历元素,增强for循环的使用详解及代码

    Android java程序员必备技能,集合与数组中遍历元素, 增强for循环的使用详解及代码 作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 For ...

  8. Java程序员必备的10个大数据框架!

    作者:java妞妞 blog.csdn.net/javaniuniu/article/details/71250316 当今IT开发人员面对的最大挑战就是复杂性,硬件越来越复杂,OS越来越复杂,编程语 ...

  9. 干货收藏 | Java 程序员必备的一些流程图

    阅读本文大概需要 6 分钟. 转载自:https://juejin.im/post/5d214639e51d4550bf1ae8df 1.Spring 的生命周期 Spring 作为当前 Java 最 ...

随机推荐

  1. IDEA2017.3.4破解方式

    下载jetbrainsCrack-2.7-release-str.jar包 下载地址: https://files.cnblogs.com/files/xifenglou/JetBrains.zip ...

  2. ios、移动端 input type=date无法点击的问题解决方法

    正常用input type = "text",获取焦点的时候,将type 改成 date即可. <div class="form-item"> &l ...

  3. [COGS 2401]Time is Money

    Description 题库链接 给你 \(n\) 个节点 \(m\) 条边的无向连通图.每条边有两个权值 \(c,t\) ,要你生成一棵边集为 \(\mathbb{E}\) 的生成树使得 \[\su ...

  4. Codeforces Round #430 C. Ilya And The Tree

    Ilya is very fond of graphs, especially trees. During his last trip to the forest Ilya found a very ...

  5. [模版]平衡树splay2

    题目描述 1. 加入:一个新的成员加入同好会,我会分配给他一个没有使用的id,并且询问他的兴趣值val. 2. 修改:id在区间[a,b]内的成员,兴趣值同时改变k,k有可能是负数,表示他们失去了对同 ...

  6. ●POJ 2284 That Nice Euler Circuit

    题链: http://poj.org/problem?id=2284 题解: 计算几何,平面图的欧拉定理 欧拉定理:设平面图的定点数为v,边数为e,面数为f,则有 v+f-e=2 即 f=e-v+2 ...

  7. 最小公共祖先 (Tarjan) POJ1470

    POJ 1470 标准的LCA,输入感觉怪怪的=.= 自己看了下Tarjan,再参考了下别人的处理方法(感觉自己好弱..) #include <iostream> #include < ...

  8. HDU2222 自动机(学习中)

    题目大意: 给你很多个单词,然后给你一篇文章,问给出的单词在文章中出现的次数. 解题思路: AC自动机入门题.需要注意的就是可能有重复单词 代码如下: #include<iostream> ...

  9. Android通过聚合数据API实现天气预报

    使用聚合数据的API 聚合数据地址:https://www.juhe.cn/ 在数据服务->生活常用->全国天气预报,申请天气预报的API使用的KEY 保存请求示例的地址,把您申请的KEY ...

  10. Scrapy框架

    Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中.其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以 ...