欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java 线程池原理深入分析

程序员文章站 2024-03-06 09:44:01
java 线程池原理 executor框架的两级调度模型 在hotspot vm的模型中,java线程被一对一映射为本地操作系统线程。java线程启动时会创建一个本地操...

java 线程池原理

executor框架的两级调度模型

在hotspot vm的模型中,java线程被一对一映射为本地操作系统线程。java线程启动时会创建一个本地操作系统线程,当java线程终止时,对应的操作系统线程也被销毁回收,而操作系统会调度所有线程并将它们分配给可用的cpu。

在上层,java程序会将应用分解为多个任务,然后使用应用级的调度器(executor)将这些任务映射成固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。

executor框架类图

Java 线程池原理深入分析

在前面介绍的java线程既是工作单元,也是执行机制。而在executor框架中,我们将工作单元与执行机制分离开来。runnable和callable是工作单元(也就是俗称的任务),而执行机制由executor来提供。这样一来executor是基于生产者消费者模式的,提交任务的操作相当于生成者,执行任务的线程相当于消费者。

1、从类图上看,executor接口是异步任务执行框架的基础,该框架能够支持多种不同类型的任务执行策略。

public interface executor {

  void execute(runnable command);
}

executor接口就提供了一个执行方法,任务是runnbale类型,不支持callable类型。

2、executorservice接口实现了executor接口,主要提供了关闭线程池和submit方法:

public interface executorservice extends executor {

  list<runnable> shutdownnow();


  boolean isterminated();


  <t> future<t> submit(callable<t> task);

 }

另外该接口有两个重要的实现类:threadpoolexecutor与scheduledthreadpoolexecutor。

其中threadpoolexecutor是线程池的核心实现类,用来执行被提交的任务;而scheduledthreadpoolexecutor是一个实现类,可以在给定的延迟后运行任务,或者定期执行命令。

在上一篇文章中,我是使用threadpoolexecutor来通过给定不同的参数从而创建自己所需的线程池,但是在后面的工作中不建议这种方式,推荐使用exectuors工厂方法来创建线程池

这里先来区别线程池和线程组(threadgroup与threadpoolexecutor)这两个概念:

a、线程组就表示一个线程的集合。

b、线程池是为线程的生命周期开销问题和资源不足问题提供解决方案,主要是用来管理线程。

executors可以创建3种类型的threadpoolexecutor:singlethreadexecutor、fixedthreadexecutor和cachedthreadpool

a、singlethreadexecutor:单线程线程池

executorservice threadpool = executors.newsinglethreadexecutor();

public static executorservice newsinglethreadexecutor() {
    return new finalizabledelegatedexecutorservice
      (new threadpoolexecutor(1, 1,
                  0l, timeunit.milliseconds,
                  new linkedblockingqueue<runnable>()));
  }

我们从源码来看可以知道,单线程线程池的创建也是通过threadpoolexecutor,里面的核心线程数和线程数都是1,并且工作队列使用的是*队列。由于是单线程工作,每次只能处理一个任务,所以后面所有的任务都被阻塞在工作队列中,只能一个个任务执行。

b、fixedthreadexecutor:固定大小线程池

executorservice threadpool = executors.newfixedthreadpool(5);

public static executorservice newfixedthreadpool(int nthreads) {
    return new threadpoolexecutor(nthreads, nthreads,
                   0l, timeunit.milliseconds,
                   new linkedblockingqueue<runnable>());
  }

这个与单线程类似,只是创建了固定大小的线程数量。

c、cachedthreadpool:*线程池

executorservice threadpool = executors.newcachedthreadpool();

public static executorservice newcachedthreadpool() {
    return new threadpoolexecutor(0, integer.max_value,
                   60l, timeunit.seconds,
                   new synchronousqueue<runnable>());
  }

*线程池意味着没有工作队列,任务进来就执行,线程数量不够就创建,与前面两个的区别是:空闲的线程会被回收掉,空闲的时间是60s。这个适用于执行很多短期异步的小程序或者负载较轻的服务器。

callable、future、futuretash详解

callable与future是在java的后续版本中引入进来的,callable类似于runnable接口,实现callable接口的类与实现runnable的类都是可以被线程执行的任务。

三者之间的关系:

callable是runnable封装的异步运算任务。

future用来保存callable异步运算的结果

futuretask封装future的实体类

1、callable与runnbale的区别

a、callable定义的方法是call,而runnable定义的方法是run。

b、call方法有返回值,而run方法是没有返回值的。

c、call方法可以抛出异常,而run方法不能抛出异常。

2、future

future表示异步计算的结果,提供了以下方法,主要是判断任务是否完成、中断任务、获取任务执行结果

public interface future<v> {
 
   boolean cancel(boolean mayinterruptifrunning);
 
   boolean iscancelled();
 
   boolean isdone();
 
   v get() throws interruptedexception, executionexception;
 
   v get(long timeout, timeunit unit)
     throws interruptedexception, executionexception, timeoutexception;
 }

3、futuretask

可取消的异步计算,此类提供了对future的基本实现,仅在计算完成时才能获取结果,如果计算尚未完成,则阻塞get方法。

public class futuretask<v> implements runnablefuture<v>

public interface runnablefuture<v> extends runnable, future<v>

futuretask不仅实现了future接口,还实现了runnable接口,所以不仅可以将futuretask当成一个任务交给executor来执行,还可以通过thread来创建一个线程。

callable与futuretask

定义一个callable的任务:

public class mycallabletask implements callable<integer>
 {
   @override
   public integer call()
     throws exception
   {
     system.out.println("callable do somothing");
     thread.sleep(5000);
     return new random().nextint(100);
   }
 }

public class callabletest
 {
   public static void main(string[] args) throws exception
   {
     callable<integer> callable = new mycallabletask();
     futuretask<integer> future = new futuretask<integer>(callable);
     thread thread = new thread(future);
     thread.start();
     thread.sleep(100);
     //尝试取消对此任务的执行
     future.cancel(true);
     //判断是否在任务正常完成前取消
     system.out.println("future is cancel:" + future.iscancelled());
     if(!future.iscancelled())
     {
       system.out.println("future is cancelled");
     }
     //判断任务是否已完成
     system.out.println("future is done:" + future.isdone());
     if(!future.isdone())
     {
       system.out.println("future get=" + future.get());
     }
     else
     {
       //任务已完成
       system.out.println("task is done");
     }
   }
 }

执行结果:

callable do somothing
future is cancel:true
future is done:true
task is done

这个demo主要是通过调用futuretask的状态设置的方法,演示了状态的变迁。

a、第11行,尝试取消对任务的执行,该方法如果由于任务已完成、已取消则返回false,如果能够取消还未完成的任务,则返回true,该demo中由于任务还在休眠状态,所以可以取消成功。

future.cancel(true);

b、第13行,判断任务取消是否成功:如果在任务正常完成前将其取消,则返回true

system.out.println("future is cancel:" + future.iscancelled());

c、第19行,判断任务是否完成:如果任务完成,则返回true,以下几种情况都属于任务完成:正常终止、异常或者取消而完成。

我们的demo中,任务是由于取消而导致完成。

 system.out.println("future is done:" + future.isdone());

d、在第22行,获取异步线程执行的结果,我这个demo中没有执行到这里,需要注意的是,future.get方法会阻塞当前线程, 直到任务执行完成返回结果为止。

system.out.println("future get=" + future.get());

callable与future

public class callablethread implements callable<string>
{
  @override
  public string call()
    throws exception
  {
    system.out.println("进入call方法,开始休眠,休眠时间为:" + system.currenttimemillis());
    thread.sleep(10000);
    return "今天停电";
  }

  public static void main(string[] args) throws exception
  {
    executorservice es = executors.newsinglethreadexecutor();
    callable<string> call = new callablethread();
    future<string> fu = es.submit(call);
    es.shutdown();
    thread.sleep(5000);
    system.out.println("主线程休眠5秒,当前时间" + system.currenttimemillis());
    string str = fu.get();
    system.out.println("future已拿到数据,str=" + str + ";当前时间为:" + system.currenttimemillis());
  }
}

执行结果:

进入call方法,开始休眠,休眠时间为:1478606602676
主线程休眠5秒,当前时间1478606608676
future已拿到数据,str=今天停电;当前时间为:1478606612677

这里的future是直接扔到线程池里面去执行的。由于要打印任务的执行结果,所以从执行结果来看,主线程虽然休眠了5s,但是从call方法执行到拿到任务的结果,这中间的时间差正好是10s,说明get方法会阻塞当前线程直到任务完成。

通过futuretask也可以达到同样的效果:

public static void main(string[] args) throws exception
  {
   executorservice es = executors.newsinglethreadexecutor();
   callable<string> call = new callablethread();
   futuretask<string> task = new futuretask<string>(call);
   es.submit(task);
   es.shutdown();
   thread.sleep(5000);
   system.out.println("主线程等待5秒,当前时间为:" + system.currenttimemillis());
   string str = task.get();
   system.out.println("future已拿到数据,str=" + str + ";当前时间为:" + system.currenttimemillis());
  }

以上的组合可以给我们带来这样的一些变化:

如有一种场景中,方法a返回一个数据需要10s,a方法后面的代码运行需要20s,但是这20s的执行过程中,只有后面10s依赖于方法a执行的结果。如果与以往一样采用同步的方式,势必会有10s的时间被浪费,如果采用前面两种组合,则效率会提高:

1、先把a方法的内容放到callable实现类的call()方法中

2、在主线程中通过线程池执行a任务

3、执行后面方法中10秒不依赖方法a运行结果的代码

4、获取方法a的运行结果,执行后面方法中10秒依赖方法a运行结果的代码

这样代码执行效率一下子就提高了,程序不必卡在a方法处。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!