java java中线程池的使用详解

  由于线程的生命周期中包括創建、就绪、运行、阻塞、销毁阶段当我们待处理的任务数目较小时,我们可以自己创建几个线程来处理相应的任务但当有大量的任務时,由于创建、销毁线程需要很大的开销运用java中线程池的使用这些问题就大大的缓解了。

  我们只需要运用Executors类给我们提供的静态方法就可以创建相应的java中线程池的使用:

  newSingleThreadExecutor返回以个包含单线程的Executor,将多个任务交给此Exector时,这个线程处理完一个任务后接着处理下一个任務若该线程出现异常,将会有一个新的线程来替代

  newFixedThreadPool返回一个包含指定数目线程的java中线程池的使用,如果任务数量多于线程数目那么没有没有执行的任务必须等待,直到有任务完成为止

  newCachedThreadPool根据用户的任务数创建相应的线程来处理,该java中线程池的使用不会对线程數目加以限制完全依赖于JVM能创建线程的数量,可能引起内存不足

  我们只需要将待执行的任务放入run方法中即可,将Runnable接口的实现类交給java中线程池的使用的execute方法作为它的一个参数,如下所示:

  如果需要给任务传递参数可以通过创建一个Runnable接口的实现类来完成。 

  丅面我们通过一个实例来说明java中线程池的使用的使用方法该实例模仿子HADOOP中作业初始化过程,也即利用java中线程池的使用从队列中取出作业並对作业进行初始化其代码如下:

  其中内部类Manager为一个线程负责从队列中获取作业,并交给java中线程池的使用去处理任务有一个线程專门将数据放入到队列中,也即每隔1ms向队列中放入10个数据

这篇文章主要介绍了java中常见的6种javaΦ线程池的使用示例文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值需要的朋友们下面随着小编來一起学习学习吧

  • 之前我们介绍了java中线程池的使用的四种拒绝策略,了解了java中线程池的使用参数的含义那么今天我们来聊聊Java 中常见的几種java中线程池的使用,以及在jdk7 加入的 ForkJoin 新型java中线程池的使用
  • 首先我们列出Java 中的六种java中线程池的使用如下
核心线程数与最大线程数相同
指定核心線程数的定时java中线程池的使用
JDK 7 新加入的一种java中线程池的使用

在了解集中java中线程池的使用时我们先来熟悉一下主要几个类的关系 ThreadPoolExecutor 的类图,鉯及 Executors 的主要方法:

上面看到的类图方便帮助下面的理解和查看,我们可以看到一个核心类 ExecutorService , 这是我们java中线程池的使用都实现的基类我们接下来说的都是它的实现类。

 

我们可以看到方法内创建线程调用的实际是 ThreadPoolExecutor 类这是java中线程池的使用的核心执行器,传入的 nThread 参数作为核心线程数和最大线程数传入队列采用了一个链表结构的有界队列。

  • 这种java中线程池的使用我们可以看作是固定线程数的java中线程池的使用它只囿在开始初始化的时候线程数会从0开始创建,但是创建好后就不再销毁而是全部作为常驻java中线程池的使用,这里如果对java中线程池的使用參数不理解的可以看之前文章 《》
  • 对于这种java中线程池的使用他的第三个和第四个参数是没意义,它们是空闲线程存活时间这里都是常駐不存在销毁,当线程处理不了时会加入到阻塞队列这是一个链表结构的有界阻塞队列,最大长度是Integer. MAX_VALUE
 
  • 上述代码中我们发现它有一个重载函数传入了一个ThreadFactory 的参数,一般在我们开发中会传入我们自定义的线程创建工厂如果不传入则会调用默认的线程工厂
  • 我们可以看到它与 FixedThreadPool javaΦ线程池的使用的区别仅仅是核心线程数和最大线程数改为了1,也就是说不管任务多少它只会有唯一的一个线程去执行
  • 如果在执行过程Φ发生异常等导致线程销毁,java中线程池的使用也会重新创建一个线程来执行后续的任务
  • 这种java中线程池的使用非常适合所有任务都需要按被提交的顺序来执行的场景是个单线程的串行。

cachedThreadPool java中线程池的使用的特点是它的常驻核心线程数为0正如其名字一样,它所有的县城都是临時的创建关于它的实现在 Executors#newCachedThreadPool() 中,代码如下:

 
  • 从上述代码中我们可以看到 CachedThreadPool java中线程池的使用中最大线程数为 Integer.MAX_VALUE , 意味着他的线程数几乎可以无限增加。
  • 因为创建的线程都是临时线程所以他们都会被销毁,这里空闲 线程销毁时间是60秒也就是说当线程在60秒内没有任务执行则销毁
  • 这裏我们需要注意点,它使用了 SynchronousQueue 的一个阻塞队列来存储任务这个队列是无法存储的,因为他的容量为0它只负责对任务的传递和中转,效率会更高因为核心线程都为0,这个队列如果存储任务不存在意义
 
 

从上面代码我们可以看到和其他java中线程池的使用创建并没有差异,只昰这里的任务队列是 DelayedWorkQueue 关于阻塞丢列我们下篇文章专门说这里我们先创建一个周期性的java中线程池的使用来看一下

 // 1. 延迟一定时间执行一次
 // 2. 按照固定频率周期执行
 //3. 按照固定频率周期执行
 

上面代码是我们简单创建了 newScheduledThreadPool ,同时演示了里面的三个核心方法首先看执行的结果:

首先我们看第一个方法 schedule , 它有三个参数,第一个参数是线程任务第二个 delay 表示任务执行延迟时长,第三个 unit 表示延迟时间的单位如上面代码所示就是延迟两秒后执行任务

 

第二个方法是 scheduleAtFixedRate 如下, 它有四个参数, command 参数表示执行的线程任务 initialDelay 参数表示第一次执行的延迟时间, period 参数表示第一次执行の后按照多久一次的频率来执行最后一个参数是时间单位。如上面案例代码所示表示两秒后执行第一次,之后按每隔三秒执行一次

 

第彡个方法是 scheduleWithFixedDelay 如下它与上面方法是非常类似的,也是周期性定时执行, 参数含义和上面方法一致这个方法和 scheduleAtFixedRate 的区别主要在于时间的起点计時不同

 

scheduleAtFixedRate 是以任务开始的时间为时间起点来计时,时间到就执行第二次任务与任务执行所花费的时间无关;而 scheduleWithFixedDelay 是以任务执行结束的时间点莋为计时的开始。如下所示

 

上面我们介绍了五种常见的java中线程池的使用对于这些java中线程池的使用我们可以从核心线程数、最大线程数、存活时间三个维度进行一个简单的对比,有利于我们加深对这几种java中线程池的使用的记忆 

0
0 0 0 0

ForkJoinPool ForkJoinPool 这是一个在 JDK7 引入的新新java中线程池的使用,它的主要特点是可以充分利用多核 CPU , 可以把一个任务拆分为多个子任务这些子任务放在不同的处理器上并行执行,当这些子任务执行结束后再紦这些结果合并起来这是一种分治思想。 ForkJoinPool 也正如它的名字一样第一步进行 Fork 拆分,第二步进行 Join 合并我们先来看一下它的类图结构

下面峩们通过一个简单的代码先来看一下如何使用 ForkJoinPool java中线程池的使用

 目标: 打印0-200以内的数字,进行分段每个间隔为10以上测试forkjoin
 //让线程阻塞等待所囿任务完成 在进行关闭
 // 如果分裂的两者差值小于10 则不再继续,直接打印
 //创建两个子任务以递归思想,
 //执行任务 fork() 表示异步的开始执行
 

从仩面的案例我们可以看到我们,创建了很多个线程执行因为我测试的电脑是12线程的,所以这里实际是创建了12个线程也侧面说明了充分調用了每个处理的线程处理能力 上面案例其实我们发现很熟悉的味道,那就是以前接触过的递归思想将上面的案例图像化如下,更直观嘚看到

上面的例子是无返回值的案例,下面我们来看一个典型的有返回值的案例相信大家都听过及很熟悉斐波那契数列,这个数列有個特点就是最后一项的结果等于前两项的和如: 0,11,23,5...f(n-2)+f(n-1) , 即第0项为0 第一项为1,则第二项为 0+1=1 以此类推。我们最初的解决方法就是使鼡递归来解决如下计算第n项的数值:

 

从上面简单代码中可以看到,当 n<=1 时返回 n , 如果 n>1 则计算前一项的值 f1 在计算前两项的值 f2 , 再将两者相加得箌结果,这就是典型的递归问题也是对应我们的 ForkJoin 的工作模式,如下所示根节点产生子任务,子任务再次衍生出子子任务到最后在进荇整合汇聚,得到结果

我们通过 ForkJoinPool 来实现斐波那契数列的计算,如下展示:

 //计算第二是项的数值
 // 获取结果这里获取的就是异步任务的最終结果
 //获取前两项的结果来计算和

通过 ForkJoinPool 可以极大的发挥多核处理器的优势,尤其非常适合用于递归的场景例如树的遍历、最优路径搜索等场景。 上面说的是 ForkJoinPool 的使用上的下面我们来说一下其内部的构造,对于我们前面说的几种java中线程池的使用来说它们都是里面只有一个隊列,所有的线程共享一个但是在 ForkJoinPool 中,其内部有一个共享的任务队列除此之外每个线程都有一个对应的双端队列 Deque , 当一个线程中任务被 Fork 汾裂了,那么分裂出来的子任务就会放入到对应的线程自己的 Deque 中而不是放入公共队列。这样对于每个线程来说成本会降低很多可以直接从自己线程的队列中获取任务而不需要去公共队列中争夺,有效的减少了线程间的资源竞争和切换

有一种情况,当线程有多个如 t1,t2,t3... 在某一段时间线程 t1 的任务特别繁重,分裂了数十个子任务但是线程 t0 此时却无事可做,它自己的 deque 队列为空这时为了提高效率, t0 就会想办法幫助 t1 执行任务这就是“ work-stealing ”的含义。 双端队列 deque 中线程 t1 获取任务的逻辑是后进先出,也就是 LIFO(Last In Frist Out) 而线程 t0 在“ steal ”偷线程 t1 的 deque 中的任务的逻辑昰先进先出,也就是 FIFO(Fast In Frist Out) 如图所示,图中很好的描述了两个线程使用双端队列分别获取任务的情景你可以看到,使用 “ work-stealing ” 算法和双端隊列很好地平衡了各线程的负载

到此这篇关于java中常见的6种java中线程池的使用示例详解的文章就介绍到这了,更多相关java常见java中线程池的使用内嫆请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

我要回帖

更多关于 java中线程池的使用 的文章

 

随机推荐