zoukankan      html  css  js  c++  java
  • java多线程开发,Executors、FutureTask、Callable

       java多线程如何应用呢,几乎学java的同学都知道Thread类和Runable接口。继承Thread类或者实现Runable接口,调用thread的start方法即可启动线程。

       然后是线程池,就是启动一系列的线程,当需要启动某个线程时,从线程池中拿取一个线程。

       最近使用到需要启动一个线程进行复杂运算并且得到其返回值。

     就用到Callable。

      

    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }

    Callable 接口是从jdk1.5之后才有的,其使用call方法代替run方法,相较Runable:可以有返回值,也可以抛出异常,这两点是引入Callable的主要原因

    Callable应该如何用才会有返回值呢:先有个例子 在来一一讲解;

        ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<String> future = executor.submit(new MyTask());
            String result = null;
            try {
                result= (String) future.get(3, TimeUnit.MINUTES);  
            } catch (TimeoutException e) {
                log.error("TimeoutException!");
            } catch (InterruptedException e) {
                log.error("InterruptedException:" + e.getMessage());
            } catch (ExecutionException e) {
                log.error("ExecutionException:" + e.getMessage());
            }

    (一).Executors的用法

      Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

    要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

    1. newSingleThreadExecutor

    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

    2.newFixedThreadPool

    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

    3. newCachedThreadPool

    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

    那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

     

    4.newScheduledThreadPool

    创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

    我们使用了newSingleThreadExecutor 来创建一个单线程的线程池,翻一下源码看里面做了什么事

     public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }

    ok,创建了一个代理线程池来处理单线程的问题,其中扔进去一个只有一个线程并且最大值为1的线程池。

    代理线程池先不用说,其实里面调用的也是完全的ThreadPoolExeutor的方法,先来解析一下ThreadPoolExeutor的参数:

    ThreadPoolExecutor的完整构造方法的签名是:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) .

    corePoolSize - 池中所保存的线程数,包括空闲线程。

    maximumPoolSize-池中允许的最大线程数。

    keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

    unit - keepAliveTime 参数的时间单位。

    workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。

    threadFactory - 执行程序创建新线程时使用的工厂。

    handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

    ThreadPoolExecutor是Executors类的底层实现。

     

    (二).executor.submit

       既然用到了submit方法,就看一看submit里面做了什么事情:

     public <T> Future<T> submit(Callable<T> task) {
                return e.submit(task);
            }
    //这个是DelegatedExecutorService 的方法,这个类是FinalizableDelegatedExecutorService的父类,
    //看到他其实是调用ThreadPoolExecutor的submit方法,其参数为Callable类型。T为返回值类型
      public <T> Future<T> submit(Callable<T> task) {
            if (task == null) throw new NullPointerException();
            RunnableFuture<T> ftask = newTaskFor(task);
            execute(ftask);
            return ftask;
        }
       protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
            return new FutureTask<T>(callable);
        }
    //这是AbstractExecutorService类中的方法,其是ThreadPoolExecutor的父类,同时实现了ExecutorService接口

    于是乎,FutureTask实现了Runable接口,所以实现Callable的过程其实就是启动了一个Runable来执行Callable,以方便更好的控制。

    void innerRun() {
                if (!compareAndSetState(0, RUNNING))
                    return;
                try {
                    runner = Thread.currentThread();
                    if (getState() == RUNNING) // recheck after setting thread
                        innerSet(callable.call());
                    else
                        releaseShared(0); // cancel
                } catch (Throwable ex) {
                    innerSetException(ex);
                }
            }
    //这个是FutureTask中的run()的实现,在这里它讲call()方法的返回值赋值给了result,并且吃掉了异常,至于为什么我们后面说

    FutureTask是实现了Future接口的,Future接口主要有这几种方法来控制其Callable任务:
        A、boolean cancel(Boolean mayInterruptlfRunning):试图取消该Future里关联的Callable任务
        B、V get():返回Callable任务里的call方法的返回值,调用该方法将导致线程阻塞,必须等到子线程结束才得到返回值
        C、V get(long timeout, TimeUnit unit):返回Callable任务里的call方法的返回值,该方法让程序最多阻塞timeout和unit指定的时间。
            如果经过指定时间后Callable任务依然没有返回值,将会抛出TimeoutException。
        D、boolean isCancelled:如果在Callable任务正常完成前被取消,则返回true。
        E、boolean isDone:如果Callable任务已经完成,则返回true

    其主要是get()方法,即获取线程的返回值的方法。这个是阻塞的,即当调用get的时候会等待线程结束才能有返回,否则就一直等待,或者等待超过超时时间。

    还记得run的时候吃掉了异常么,那里吃掉的异常将会在这里抛出来:所以,如果不调用get()方法,则执行的call方法是不抛出异常的,也没有返回值,即跟普通的Runable一样的。

    //这个是get的主要实现方法  
    V innerGet(long nanosTimeout) throws InterruptedException, ExecutionException, TimeoutException { if (!tryAcquireSharedNanos(0, nanosTimeout)) throw new TimeoutException(); if (getState() == CANCELLED) throw new CancellationException(); if (exception != null) throw new ExecutionException(exception); return result; }

    (三):ExecutorService还提供了一些其他的方法,如中断线程。。

    (四):摘抄一些

    下面介绍一下几个类的源码:

    ExecutorService  newFixedThreadPool (int nThreads):固定大小线程池。

    可以看到,corePoolSize和maximumPoolSize的大小是一样的(实际上,后面会介绍,如果使用无界queue的话maximumPoolSize参数是没有意义的),keepAliveTime和unit的设值表名什么?-就是该实现不想keep alive!最后的BlockingQueue选择了LinkedBlockingQueue,该queue有一个特点,他是无界的。

    1.     public static ExecutorService newFixedThreadPool(int nThreads) {   

    2.             return new ThreadPoolExecutor(nThreads, nThreads,   

    3.                                           0L, TimeUnit.MILLISECONDS,   

    4.                                           new LinkedBlockingQueue<Runnable>());   

    5.         }

    ExecutorService  newSingleThreadExecutor():单线程

    1.     public static ExecutorService newSingleThreadExecutor() {   

    2.             return new FinalizableDelegatedExecutorService   

    3.                 (new ThreadPoolExecutor(1, 1,   

    4.                                         0L, TimeUnit.MILLISECONDS,   

    5.                                         new LinkedBlockingQueue<Runnable>()));   

    6.         }

    ExecutorService newCachedThreadPool():无界线程池,可以进行自动线程回收

    这个实现就有意思了。首先是无界的线程池,所以我们可以发现maximumPoolSize为big big。其次BlockingQueue的选择上使用SynchronousQueue。可能对于该BlockingQueue有些陌生,简单说:该QUEUE中,每个插入操作必须等待另一个线程的对应移除操作。

    1.     public static ExecutorService newCachedThreadPool() {   

    2.             return new ThreadPoolExecutor(0, Integer.MAX_VALUE,   

    3.                                           60L, TimeUnit.SECONDS,   

    4.                                           new SynchronousQueue<Runnable>());   

    1.     }

    先从BlockingQueue<Runnable> workQueue这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。

    所有BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

    如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)

    如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程

    如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

    queue上的三种类型。

     

    排队有三种通用策略:

    直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

    无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

    有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量

  • 相关阅读:
    jq 的简单循环
    简单的下拉菜单
    jQ事件
    图片移动
    选中效果
    js 时间
    简单的密码验证
    DOM 的简介 和一些方法
    Java编程思想笔记
    Java并发笔记(二)
  • 原文地址:https://www.cnblogs.com/lic309/p/4442189.html
Copyright © 2011-2022 走看看