Executor线程池框架

Posted yuanfei1110111

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Executor线程池框架相关的知识,希望对你有一定的参考价值。

一、要引入Executor线程池框架的原因
1.new Thread()的缺点
(1)每次调用new Thread()都会耗费性能;
(2)通过new Thread()创建的线程缺乏管理,被称为野线程,可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪,以及不能进行定时执行、定期执行、线程中断等。
2.采用线程池的优点
(1)重用存在的线程,减少对象创建、消亡的开销,性能佳;
(2)可对并发线程进行更好的管理,有效控制并发线程数,提高系统资源使用率,避免过多资源竞争,可进行定时执行、定期执行、线程中断等操作。

二、Executor的概述
Executor框架是在Java5中引入的,其内部使用了线程池机制,在java.util.concurrent包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好外,还有关键的一点:有助于避免this逃逸问题:在构造器构造还未彻底完成之前,将自身this引用向外抛出并被其它线程访问,可能会被访问到还未被初始化到的变量,甚至可能会造成更严重的问题。

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

三、Executors工厂类
通过Executors提供四种线程池,newFixedThreadPool、newCachedThreadPool、newSingleThreadPool、newScheduledThreadPool。
1.public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池,以共享的无界队列方式来运行这些线程。示例如下:
ExecutorService service = Executors.newFixedThreadPool(5);
    for (int i=0; i<20; i++) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
                
        };
        service.execute(r);
    }
}
运行结果:总共只会创建5个线程,开始执行五个线程,当五个线程都处于活动状态,再次提交的任务都会加入队列等到其它线程运行结束,当线程处于空闲状态时会被下一个任务复用。
2.public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用),如果当前没有可用线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60s钟未被使用的线程。示例如下:
ExecutorService service = Executors.newCachedThreadPool();
    for (int i=0; i<100; i++) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
                
        };
        service.execute(r);
    }
        
}
可以看出缓存线程池大小是不定值,可以创建不同数量的线程,在使用缓存池时,先查看有没有以前创建的线程,如果有,就复用,如果没有,就新建新的加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务。
3.public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor,只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO/LIFO/优先级)执行,示例如下:
ExecutorService service = Executors.newSingleThreadExecutor();
    for (int i=0; i<20; i++) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
                
        };
        service.execute(r);
    }
        
}
运行结果:只会创建一个线程,当上一个执行完之后才会执行第二个。
4.public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性任务执行的定长线程池,多数情况下可用来代替Timer类,示例如下:
(1)schedule(Runnable command, long delay, TimeUnit unit):
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
    for (int i=0; i<20; i++) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
                
        };
        service.schedule(r, 5000, TimeUnit.MILLISECONDS);
    }
        
}
运行结果和newFixedThreadPool类似,不同的是newScheduledThreadPool是延时一定时间之后才执行。
(2)scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):执行时刻分别为initialDelay、initialDelay+period、initialDelay+period*2,以此类推。
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
    for (int i=0; i<20; i++) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
                
        };
        service.scheduleAtFixedRate(r, 5000, 3000, TimeUnit.MILLISECONDS);
    }
        
}
(3)scheduleWithFixedDelay(Runnable command, long initialDealy, long delay, TimeUnit unit):创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在任务执行完成后再延迟固定时间后再执行下一次。
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
    for (int i=0; i<20; i++) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
                
        };
        service.scheduleWithFixedDelay(r, 5000, 3000, TimeUnit.MILLISECONDS);
    }
        
}

四、ExecutorService
public interface ExecutorService extends Executor {
    void shutdown(); //顺次地关闭ExecutorService,停止接收新任务,等待所有已经提交的任务执行完毕后,关闭ExecutorService;
    List<Runnable> shutdownNow(); //阻止等待任务启动并试图停止当前正在执行的任务,停止接收新的任务,返回处于等待的任务列表;
    boolean isTerminated(); //如果关闭后所有任务都已完成,则返回true(除非首先调用shutdown或shutdownNow,否则isTerminated永不为true);
    (其余方法见url)
}
ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,可以调用ExecutorService的shutdown方法来平滑地关闭ExecutorService,调用该方法后,将导致ExecutorService停止接收任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分为两类,一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService,因此我们一般用该接口来实现和管理多线程。
ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown方法时,便进入关闭状态,此时意味着ExecutorService不再接收新的任务,但它还在执行已经提交了的任务,当所有已经提交了的任务执行完成后,便达到终止状态。如果不调用shutdown方法,ExecutorService会一直处于运行状态,不断接收新的任务,服务器一般不需要关闭它,保持一直运行即可。

五、Executor执行Callable任务
在Java5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call方法只能通过ExecutorService的submit(Callable task)方法来执行,并且返回一个Future,示例代码如下:
public class Test {

    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        List<Future<String>> list = new ArrayList<Future<String>>();
        
        // 创建10个任务并执行
        for (int i=0; i<10; i++) {
            Future<String> future = service.submit(new TaskWithResult(i));
            list.add(future);
        }
        
        // 遍历任务的结果
        for (Future<String> f: list) {
            try {
                while (!f.isDone()) { //Future返回如果没有完成,则一直循环等待
                    System.out.println(f.get());
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 启动一次顺序关闭,执行以前提交的任务,但不接收新任务
                service.shutdown();
            }
        }
        
    }
    
    class TaskWithResult implements Callable<String> {

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

        /**
         * 任务的具体执行过程,一旦任务传给ExecutorService的submit方法,
         * 则该方法自动在一个线程上执行
         */
        @Override
        public String call() throws Exception {
            System.out.println("call()方法自动被调用!!!              " + Thread.currentThread().getName());
            // 该方法的返回结果将被Future的get方法得到
            return "call()方法的返回结果是  " + id + " " + Thread.currentThread().getName();
        }
        
    }
}
运行结果:summit也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。另外,需要注意:如果Future的返回尚未完成,则get方法会阻塞等待,直到Future完成返回,可以通过调用isDone方法判断Future是否完成了返回。

















































































































































































以上是关于Executor线程池框架的主要内容,如果未能解决你的问题,请参考以下文章

基础线程机制--Executor线程池框架

Executor线程池框架

Java并发——线程池Executor框架

Java并发——线程池Executor框架

Executor线程池框架

2,Executor线程池