多线程相关

 

实现多线程编程的几种方式:

  1.实现Runable接口

  2.继承Thread类

  3.使用Executor框架(JDK1.5之后)

这里着重介绍一下Executor框架.

一. Executor框架的两级调度模型

  在HotSpot VM的线程模型中,Java线程被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当Java线程终止时,这个操作系统线程也会被回收。操作系统会调用所有线程并将他们分配给可用的CPU。

  可以将此种模式分为两层,在上层,Java多线程程序通常把应用程序分解为若干任务,然后使用用户级的调度器(Executor框架)讲这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。

两级调度模型的示意图:

  

简而言之,Executor框架实现了工作单元执行单元的分离。工作单元包括Runnable 和 Callable,而执行单元由Executor框架提供。

 

Runnable和Callable区别(具体代码下面会提到)

  (1)Callable规定的方法是call(),Runnable规定的方法是run()。其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。 
  (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得 
  (3)call方法可以抛出异常,run方法不可以 
  (4)运行Callable任务可以拿到一个Future对象,c表示异步计算的结果。

 

二. Executor框架的结构

  Executor主要由三部分组成:任务产生部分,任务处理部分,结果获取部分。(设计模式:生产者与消费者模式)

  

  2.1 任务的产生:Runnable接口和Callable接口

  主线程首先要创建实现 Runnable接口或者Callable接口的任务对象.工具类Executors可以把一个Runnable对象封装为一个Callable对象。当把runnble包装成callable时就存在如何处理返回值的问题,一种就是默认返回null,一种就是让程序员自定义。后者的话如果想得到正确的处理结果,传入的result就需要同时在runnable里引用并修改,不然没有卵用。

  2.2 任务的执行

 

  包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor.

  

  2.3 异步计算的结果

包括Future和实现Future接口的FutureTask类。

Executor框架的类与接口

  • Executor是一个接口,他是Executor框架的基础,它将任务的提交与任务的执行分离。
  • ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
  • ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor 比 Timer 更灵活,功能更强大。
  • Future接口和它的实现FutureTask类,代表异步计算的结果。
  • Runnable和Callable接口的实现类,都可以被ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。

 下面一个简单的使用ThreadPoolExecutor  execute/submit 方法提交任务

ExecutorService executorService = new ThreadPoolExecutor(5, 5,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

        Runnable r = new Runnable() {
            @Override
            public void run() {

            }
        };
        Callable callable =  new Callable<String>(){
            @Override
            public String call() throws Exception {
                return "test";
            }
        };
        //callable可将runnable对象转为callable对象
        Callable<Object> callable_r = Executors.callable(r);

        //ExecutorService  execute   没有返回值
        executorService.execute(r);
        //ExecutorService submit
        Future<?> future_r = executorService.submit(r);
        Future<?> future_c = executorService.submit(r);

ThreadPoolExecutor浅析

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

corePoolSize:线程池的核心线程数,默认情况下,核心线程数会一直在线程池中存活,即使它们处理闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会执行超时策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。
maximumPoolSize:线程池所能容纳的最大线程数量,当活动线程数到达这个数值后,后续的新任务将会被阻塞。
keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。
unit:用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。
workQueue:线程池中的任务队列,通过线程池的execute方法提交Runnable对象会存储在这个队列中。
threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)。
除了上面的参数外还有个不常用的参数,RejectExecutionHandler,这个参数表示当ThreadPoolExecutor已经关闭或者ThreadPoolExecutor已经饱和时(达到了最大线程池大小而且工作队列已经满),execute方法将会调用Handler的rejectExecution方法来通知调用者,默认情况 下是抛出一个RejectExecutionException异常。了解完相关构造函数的参数,我们再来看看ThreadPoolExecutor执行任务时的大致规则:
(1)如果线程池的数量还未达到核心线程的数量,那么会直接启动一个核心线程来执行任务
(2)如果线程池中的线程数量已经达到或者超出核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
(3)如果在步骤(2)中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
(4)如果在步骤(3)中线程数量已经达到线程池规定的最大值,那么就会拒绝执行此任务,ThreadPoolExecutor会调用RejectExecutionHandler的rejectExecution方法来通知调用者。
到此ThreadPoolExecutor的详细配置了解完了,ThreadPoolExecutor的执行规则也了解完了,那么接下来我们就来介绍3种常见的线程池,它们都直接或者间接地通过配置ThreadPoolExecutor来实现自己的功能特性,这个3种线程池分别是FixedThreadPool,CachedThreadPool,ScheduledThreadPool以及SingleThreadExecutor。
 

ThreadPoolExecutor 的三种类型

1.FixedThreadPool创建固定长度的线程池,每次提交任务创建一个线程,直到达到线程池的最大数量,线程池的大小不再变化。这个线程池可以创建固定线程数的线程池。特点就是可以重用固定数量线程的线程池

public static ExecutorService newFixedThreadPool(int nThreads) { 
        return new ThreadPoolExecutor(nThreads, nThreads, 0L,
                                      TimeUnit.MILLISECONDS, 
                                      new LinkedBlockingQueue<Runnable>()); 
} 
  • FixedThreadPool的corePoolSize和maxiumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。
  • 0L则表示当线程池中的线程数量操作核心线程的数量时,多余的线程将被立即停止
  • 最后一个参数表示FixedThreadPool使用了无界队列LinkedBlockingQueue作为线程池的做工队列,由于是无界的,当线程池的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池的线程数量不会超过corePoolSize,同时maxiumPoolSize也就变成了一个无效的参数,并且运行中的线程池并不会拒绝任务。

执行过程如下:

1.如果当前工作中的线程数量少于corePool的数量,就创建新的线程来执行任务。

2.当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。

3.线程执行完1中的任务后会从队列中去任务。

注意LinkedBlockingQueue是无界队列,所以可以一直添加新任务到线程池。

2.SingleThreadExecutor:SingleThreadExecutor是使用单个worker线程的Executor。特点是使用单个工作线程执行任务。

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

SingleThreadExecutor的corePoolSize和maxiumPoolSize都被设置1。

其他参数均与FixedThreadPool相同,其运行图如下:

执行过程如下:

1.如果当前工作中的线程数量少于corePool的数量,就创建一个新的线程来执行任务。

2.当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。

3.线程执行完1中的任务后会从队列中去任务。

注意:由于在线程池中只有一个工作线程,所以任务可以按照添加顺序执行

 

3.CachedThreadPool: CachedThreadPool是一个”无限“容量的线程池,它会根据需要创建新线程。特点是可以根据需要来创建新的线程执行任务,没有特定的corePool

 

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

 

CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximum是无界的。这里keepAliveTime设置为60秒,意味着空闲的线程最多可以等待任务60秒,否则将被回收。

 
CachedThreadPool使用没有容量的SynchronousQueue作为主线程池的工作队列,它是一个没有容量的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作。这意味着,如果主线程提交任务的速度高于线程池中处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU资源。其运行图如下:

可以使用Executors创建上述3种类型

//FixedThreadPool
Executors.newFixedThreadPool(10);
//SingleThreadExecutor
Executors.newSingleThreadExecutor();
//CachedThreadPool
Executors.newCachedThreadPool();

 

使用ExecutorCompletionService获取执行结果

对于Callable对象与Runnable最大的不同就是有返回值,这里可以使用ExecutorCompletionService获取线程执行结果

  public static void testExecutor() throws Exception{
        ExecutorService pool = Executors.newCachedThreadPool();
        CompletionService<Integer> cService = new ExecutorCompletionService<Integer>(pool);
        for (int i = 0; i <5 ; i++) {
            final int j = i;
        //使用submit提交任务; cService.submit(
new Callable<Integer>() { @Override public Integer call() throws Exception { Thread.sleep(1000); return j; } }); } for (int i = 0; i <5 ; i++) {
        //获取线程返回结果 System.out.println(cService.take().get()); } }
public static void main(String[] args) throws Exception { // ExecutorTest(); //testCallable(); //testPoolSubmit_ExecutorsCallable(); //testCallable_(); testExecutor(); }


返回结果:
2
1
4
3
0

还有一种方式是通过自己维护一个list来获取线程返回值;

这两者最主要的区别在于submit的task不一定是按照加入自己维护的list顺序完成的,也就是从list取出的Future不一定已经执行完了,需要做额外的判断;

从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。

而CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。

所以,先完成的必定先被取出。这样就减少了不必要的等待时间

 

posted @ 2018-01-12 15:38  北境  阅读(184)  评论(1编辑  收藏  举报