[Java并发]Java中Executor框架(四)

来源:互联网 发布:怎么申请淘宝主播 编辑:程序博客网 时间:2024/05/29 17:58

前言

在Java SE5出来的Executor框架,有利于程序员去写多线程的程序,将任务的提交和执行解耦,只需提交自己的任务,其执行与返回都不需要自己来管。
其主要有下面几个对象:

  • Executors
  • ExecutorService
  • Future
  • Callable
  • ThreadPoolExecutor

下面我们会一个个介绍。

ExecutorsExecutorService

ExecutorService可以通过Executors的一些静态方法来生成。Executors是所有Executors框架的入口。我们可以看看下面几个方法(后文会具体说明):

  • newCachedThreadPool:缓存线程池,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中。
  • newFixedThreadPool:固定尺寸的线程池。
  • newScheduledThreadPool:单例线程池。

ExecutorService存在5种状态。

  • RUNNING:在运行阶段,ExecutorService可以接受新的线程(调用其execute()或者submit()方法)。
  • SHUTDOWN:关闭状态不接受新的线程,但是会执行已经提交的线程(调用shutdown方法来进入关闭状态)。
  • STOP:不接受新的任务,不处理阻塞队列里的任务,中断正在处理的任务。
  • TIDYING:过渡状态,也就是说所有的任务都执行完了,当前线程池已经没有有效的线程,这个时候线程池的状态将会TIDYING,并且将要调用terminated()方法。
  • TERMINATED:终止状态。terminated()方法调用完成以后的状态。

这些状态的转变是这样的:
- RUNNING -> SHUTDOWN
On invocation of shutdown(), perhaps implicitly in finalize()
- (RUNNING or SHUTDOWN) -> STOP
On invocation of shutdownNow()
- SHUTDOWN -> TIDYING
When both queue and pool are empty
- STOP -> TIDYING
When pool is empty
- TIDYING -> TERMINATED
When the terminated() hook method has completed

CallableRunnbale

ExecutorService可以接受2类线程对象RunnableCallable,下面看看:

 @org.junit.Test    public void test() {        Runnable runnable = () -> System.out.println("Hello World");        ExecutorService executorService = Executors.newCachedThreadPool();        executorService.execute(runnable);        executorService.shutdown();    }

结果如下:

Hello World

然后我们看看Callable,Callable类似于Runnable,但是Callable有返回值,返回一个Future对象,然后再去调用Future里面的方法。在官网上这么定义:

A task that returns a result and may throw an exception. Implementors define a single method with no arguments called call.
The Callable interface is similar to Runnable, in that both are designed for classes whose instances are potentially executed by another thread. A Runnable, however, does not return a result and cannot throw a checked exception.

Callable类似于Runnable,但是其多了返回结果(其返回结果的类似为V,就是类签名中的泛型类型)。
Executors中可以看到用如下的方法,可以将Runnable适配成Callable

 /**     * Returns a {@link Callable} object that, when     * called, runs the given task and returns the given result.  This     * can be useful when applying methods requiring a     * {@code Callable} to an otherwise resultless action.     * @param task the task to run     * @param result the result to return     * @param <T> the type of the result     * @return a callable object     * @throws NullPointerException if task null     */    public static <T> Callable<T> callable(Runnable task, T result) {        if (task == null)            throw new NullPointerException();        return new RunnableAdapter<T>(task, result);    }    /**     * A callable that runs given task and returns given result     */    static final class RunnableAdapter<T> implements Callable<T> {        final Runnable task;        final T result;        RunnableAdapter(Runnable task, T result) {            this.task = task;            this.result = result;        }        public T call() {            task.run();            return result;        }    }

然后是对Future的一些定义:

A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation. The result can only be retrieved using method get when the computation has completed, blocking if necessary until it is ready. Cancellation is performed by the cancel method. Additional methods are provided to determine if the task completed normally or was cancelled. Once a computation has completed, the computation cannot be cancelled. If you would like to use a Future for the sake of cancellability but not provide a usable result, you can declare types of the form Future

 @org.junit.Test    public void test() {        Callable<Integer> callable = () -> 3;        FutureTask<Integer> future = new FutureTask<>(callable);        ExecutorService executorService = Executors.newCachedThreadPool();        executorService.submit(future);        try {            System.out.println(future.get());        } catch (InterruptedException | ExecutionException e) {            e.printStackTrace();        }    }

具有返回结果,其结果为

3

ThreadFactory

Executors创建默认的线程工厂类,调用如下的方法:
创建的线程的名字为pool-N-thread-M:where N is the sequence number of this factory, and M is the sequence number of the thread created by this factory.

   public static ThreadFactory defaultThreadFactory() {        return new DefaultThreadFactory();    }    /**     * The default thread factory.     */    static class DefaultThreadFactory implements ThreadFactory {        /*        2个原子计数器,用来记录线程数与线程池数。        */        private static final AtomicInteger poolNumber = new AtomicInteger(1);        private final AtomicInteger threadNumber = new AtomicInteger(1);        private final ThreadGroup group;        private final String namePrefix;        DefaultThreadFactory() {            SecurityManager s = System.getSecurityManager();            group = (s != null) ? s.getThreadGroup() :                                  Thread.currentThread().getThreadGroup();            namePrefix = "pool-" +                          poolNumber.getAndIncrement() +                         "-thread-";        }        public Thread newThread(Runnable r) {            Thread t = new Thread(group, r,                                  namePrefix + threadNumber.getAndIncrement(),                                  0);            if (t.isDaemon())                t.setDaemon(false);            if (t.getPriority() != Thread.NORM_PRIORITY)                t.setPriority(Thread.NORM_PRIORITY);            return t;        }    }

线程池

上文说到,有如下几种线程池:
- newCachedThreadPool:缓存线程池,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中。
- newFixedThreadPool:固定尺寸的线程池。
- newScheduledThreadPool:单例线程池。

下面我们一一来看看这些线程池:
newCachedThreadPool

 @org.junit.Test    public void test() {        ExecutorService executorService = Executors.newCachedThreadPool();        for (int i = 0; i < 100; i++) {            executorService.execute(new MyRunnable());        }        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        executorService.shutdown();    }    class MyRunnable implements Runnable {        @Override        public void run() {            System.out.println(Thread.currentThread().getName());        }    }

输出结果如下:

pool-1-thread-1pool-1-thread-2pool-1-thread-3pool-1-thread-4pool-1-thread-5pool-1-thread-6pool-1-thread-7pool-1-thread-8pool-1-thread-9pool-1-thread-10.....pool-1-thread-100

newFixedThreadPool

public class Test {    @org.junit.Test    public void test() {        ExecutorService executorService = Executors.newFixedThreadPool(10);        for (int i = 0; i < 100; i++) {            executorService.execute(new MyRunnable());        }        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        executorService.shutdown();    }    class MyRunnable implements Runnable {        @Override        public void run() {            System.out.println(Thread.currentThread().getName());        }    }}

输出结果如下:

......pool-1-thread-1pool-1-thread-1pool-1-thread-2pool-1-thread-3pool-1-thread-4pool-1-thread-5pool-1-thread-6pool-1-thread-7pool-1-thread-8pool-1-thread-9pool-1-thread-10

newScheduledThreadPool

public class Test {    @org.junit.Test    public void test() {        ExecutorService executorService = Executors.newSingleThreadExecutor();        for (int i = 0; i < 100; i++) {            executorService.execute(new MyRunnable());        }        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        executorService.shutdown();    }    class MyRunnable implements Runnable {        @Override        public void run() {            System.out.println(Thread.currentThread().getName());        }    }}

输出结果如下:

pool-1-thread-1pool-1-thread-1pool-1-thread-1pool-1-thread-1pool-1-thread-1pool-1-thread-1pool-1-thread-1

由此可以印证到上面说的结论,那么下面我们从源码的角度来分析为什么会出现这些情况。

 public static ExecutorService newSingleThreadExecutor() {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>()));    } public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }

这三个方法,都去构造了一个ThreadPoolExecutor,也就是线程池:

 public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,             Executors.defaultThreadFactory(), defaultHandler);    }

其参数如下:
- corePoolSize:线程池中所保存的核心线程数,包括空闲线程。
- maximumPoolSize:池中允许的最大线程数。
- keepAliveTime:线程池中的空闲线程所能持续的最长时间。
- unit:持续时间的单位。
- workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。

根据ThreadPoolExecutor源码前面大段的注释,我们可以看出,当试图通过excute方法讲一个Runnable任务添加到线程池中时,按照如下顺序来处理:
1、如果线程池中的线程数量少于corePoolSize,即使线程池中有空闲线程,也会创建一个新的线程来执行新添加的任务;
2、如果线程池中的线程数量大于等于corePoolSize,但缓冲队列workQueue未满,则将新添加的任务放到workQueue中,按照FIFO的原则依次等待执行(线程池中有线程空闲出来后依次将缓冲队列中的任务交付给空闲的线程执行);
3、如果线程池中的线程数量大于等于corePoolSize,且缓冲队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务;
4、如果线程池中的线程数量等于了maximumPoolSize,有4种才处理方式(该构造方法调用了含有5个参数的构造方法,并将最后一个构造方法为RejectedExecutionHandler类型,它在处理线程溢出时有4种方式,这里不再细说,要了解的,自己可以阅读下源码)。

下面说说几种排队的策略:
1、直接提交。缓冲队列采用 SynchronousQueue,它将任务直接交给线程处理而不保持它们。如果不存在可用于立即运行任务的线程(即线程池中的线程都在工作),则试图把任务加入缓冲队列将会失败,因此会构造一个新的线程来处理新添加的任务,并将其加入到线程池中。直接提交通常要求无界 maximumPoolSizes(Integer.MAX_VALUE) 以避免拒绝新提交的任务。newCachedThreadPool采用的便是这种策略。
2、无界队列。使用无界队列(典型的便是采用预定义容量的 LinkedBlockingQueue,理论上是该缓冲队列可以对无限多的任务排队)将导致在所有corePoolSize线程都工作的情况下将新任务加入到缓冲队列中。这样,创建的线程就不会超过 corePoolSize,也因此,maximumPoolSize的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列。newFixedThreadPool采用的便是这种策略。
3、有界队列。当使用有限的 maximumPoolSizes 时,有界队列(一般缓冲队列使用ArrayBlockingQueue,并制定队列的最大长度)有助于防止资源耗尽,但是可能较难调整和控制,队列大小和最大池大小需要相互折衷,需要设定合理的参数。

参考

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

0 0
原创粉丝点击