深入浅出JAVA线程池使用原理1

前言:

Java中的线程池是并发框架中运用最多的,几乎所有需要异步或并发执行任务的程序都可以使用线程池,线程池主要有三个好处:

1、降低资源消耗:可以重复使用已经创建的线程降低线程创建和销毁带来的消耗

2、提高响应速度:执行任务时,不需要等待线程的创建就可以直接执行任务

3、提高线程的可管理性:线程是稀缺资源,如果无限制地创建不仅会消耗系统资源,还会降低系统的稳定性,线程池可以对线程进行统一分配、调优和监控

一、线程池的实现原理

在了解线程池实现原理之前,先了解线程池的一些元素

1.核心线程池

线程池有一个核心线程池,核心线程池的大小在线程池创建时设定,默认是有任务提交时会创建线程来执行,也可以调用线程池的prestartAllCoreThreads来提前创建并启动所有的基本线程。

2.任务队列

当核心线程池中的线程数超过设置的的大小时,再有新的任务提交则会先将任务当道队列中,等待核心线程池中的线程执行当前的任务之后,再到队列中获取任务来执行

3.饱和策略

当线程池已经处于饱和状态,无法再分配线程给新的任务时,会采用饱和策略拒绝新的任务

1.1.线程池的工作流程

线程池的整体工作流程如下图示

当有新任务提交到线程池时,线程池的处理流程如下:

1.判断核心线程池是否已满,如果没满,则创建新线程来执行此任务(即使当前有空闲的线程也会直接创建,而不是使用空闲的线程来执行),直到核心线程池中的线程数达到了设置的大小之后就不再创建;

如果核心线程池已经满了,则进入下一阶段的判断

2.判断工作队列是否已经满了,如果工作队列没有满,则将任务暂时存放到工作队列中,等待核心线程池中的线程空闲下来再来获取任务执行(核心线程池中的线程执行完任务之后会循环从工作队列中取任务来执行);如果队列也满了,则进入下一阶段的判断

3.判断线程池的是否已满(线程池除了核心线程池,还设置了线程池的最大线程大小,即使核心线程池满了,还是可以再创建线程),如果线程池中工作的线程没有达到最大值,则创建新线程来执行任务;

如果线程池也已经满了, 则按照线程池的饱和策略来处理任务(具体怎么处理任务按具体的饱和策略来执行)

1.2、线程池的创建

创建线程池可以通过ThreadPoolExecutor来创建,构造方法如下

 1 public ThreadPoolExecutor(int corePoolSize,
 2                               int maximumPoolSize,
 3                               long keepAliveTime,
 4                               TimeUnit unit,
 5                               BlockingQueue<Runnable> workQueue,
 6                               ThreadFactory threadFactory,
 7                               RejectedExecutionHandler handler) {
 8         if (corePoolSize < 0 ||
 9             maximumPoolSize <= 0 ||
10             maximumPoolSize < corePoolSize ||
11             keepAliveTime < 0)
12             throw new IllegalArgumentException();
13         if (workQueue == null || threadFactory == null || handler == null)
14             throw new NullPointerException();
15         this.corePoolSize = corePoolSize;
16         this.maximumPoolSize = maximumPoolSize;
17         this.workQueue = workQueue;
18         this.keepAliveTime = unit.toNanos(keepAliveTime);
19         this.threadFactory = threadFactory;
20         this.handler = handler;
21     }

这里涉及到了7个参数,含义分别如下:

corePoolSize:核心线程池大小,核心线程池的用法上面已经提到,不再赘述

maximumPoolSize:线程池的最大数量,也就是线程池允许创建的最大线程数,如果线程池的工作队列满了,则会先判断是否达到了最大线程数,若没有则还可以再创建线程来执行新任务,直到线程数达到线程池的最大数量

keepAliveTime:保持线程活动时间,当核心线程池中的线程处于空闲状态时,不会立即销毁线程而是保持一定的活动时间来等待任务,一定程度上提高了线程的复用率。

unit:线程活动时间(keepAliveTime)的单位,可以选择DAYS(天)、(HOURS)时、(MINUTES)分、(SECONDS)秒、(MILLSECONDS)毫秒、(MICROSECONDS)微妙、(NANOSECONDS)纳秒

workQueue:工作队列,核心线程池满了,新任务会存放在工作队列中等待核心线程池中的线程来获取(后面会详细描述)

threadFactory:线程工厂,线程池可以设置指定的线程工厂来创建新线程。如果不设置,默认是使用Executors.defaultThreadFactory()来创建

handler:饱和策略,当线程池已经满了,则说明线程池已经处于饱和状态无法再接受新任务了,那么就采取饱和策略来处理新任务,默认使用AbortPolicy,表示无法处理新任务而直接抛出异常(后面会详细描述)

1.3、线程池提交任务

线程池有两个方法可以用来提交任务,分别是execute()和submit()

execute()方法用于提交不需要返回值的方法,所以无法判断任务是否被线程池执行成功

submit()方法用于提交需要返回值的方法,线程池会返回一个future类型的对象,通过future对象可以判断任务是否执行成功,并且可以通过get方法来获取线程执行的返回值,get方法会阻塞当前线程直到任务结束,也可以给get方法设置指定时长,

则达到指定时长之后会立即返回,但是此时可能线程还没有执行完。

1.4、关闭线程池

线程池关闭方法有shutDown方法和shutDownNow方法,原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,如果线程无法响应中断的任务就可能永远无法终止。

shutDown方法是将线程池的状态设置为SHUTDOWN状态,然后中断所有还没有执行的任务线程

showDownNow是将线程池中的状态设置为STOP,然后尝试停止所有正在执行或空闲的线程,并返回等待执行任务的列表

如果需要保证任务执行完,则建议使用shutDown方法来执行关闭方法

1.5、线程池的监控

线程池有多个属性可和方法来监控当前线程池的状态

taskCount:线程池需要执行的任务总数量(包括等待执行和已经执行完的)

completedTaskCount:线程池已完成的任务数量

largestPoolSize:线程池运行中创建的最大线程数,可以判断线程池运行过程中是否达到了最大线程数

getPoolSize:线程池中的线程数量

getActiveCount:获取活动的线程数

二、线程池工作队列和饱和策略

2.1、工作队列 (BlockingQueue<Runnable>),由名字可以看出线程池的工作队列是一个阻塞队列,主要有以下四种类型:

ArrayBlockingQueue:基于数组结构的有界阻塞队列,采用FIFO(先进先出)原则对任务进行排序

LinkedBlockingQueue:基于链表结构的阻塞队列,采用FIFO(先进先出)原则对任务进行排序,吞吐量要高于ArrayBlockingQueue

SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入一直处于阻塞状态,吞吐量高于LinkedBlockingQueue

PriorityBlockingQueue:一个具有优先级的无限阻塞队列

2.2、饱和策略(RejectedExecutionHandler)当线程池无法处理新任务时,就将采用饱和策略来拒绝任务的执行,主要有以下四种类型:

AbortPolicy:拒绝任务,抛出异常,也是线程池默认饱和策略

CallerRunsPolicy:拒绝任务,使用调用者的当前线程来执行此任务

DiscardOldestPolicy:丢弃工作队列中的最近一个任务,并处理当前任务

discardPolicy:不做任何处理,直接丢弃任务

三、线程池实战(测试案例)

先创建一个任务类,代码如下:

 1  public static class ThreadPoolTaskTest implements Runnable
 2     {
 3         private int taskIndex;//任务编号
 4
 5         public ThreadPoolTaskTest(int i)
 6         {
 7             taskIndex = i;
 8         }
 9
10         @Override
11         public void run()
12         {
13             try
14             {
15                 //线程睡眠2秒
16                 Thread.sleep(2000);
17                 System.out.println("线程:"+Thread.currentThread().getName()+"执行任务:"+taskIndex);
18             }
19             catch (InterruptedException e)
20             {
21                 e.printStackTrace();
22             }
23         }
24
25     }
 1 public static void main(String[] args)
 2     {
 3         /**
 4          * 1.创建线程池
 5          * 核心线程池大小:2、 最大线程池大小:4、 工作队列:有界阻塞队列,队列大小为6 线程活动保持时间:10、 活动时间单位:毫秒、 线程工厂:默认、 饱和策略:默认
 6          */
 7         ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(6));
 8         threadPoolTest(executor);
 9     }
10
11     public static void threadPoolTest(ThreadPoolExecutor executor){
12         /**
13          * 前提:核心线程数为2,队列为6,而最大线程池为4
14          * 测试:向线程池中提交10个任务
15          * 猜想:核心线程池创建2个线程执行2个任务,6个任务放到队列中然后继续等待执行,2个任务被线程池创建的其他两个线程执行
16          * */
17         for (int i = 0; i < 10; i++)
18         {
19             executor.execute(new ThreadPoolTaskTest(i));
20         }
21     }

执行结果如下:

 1 线程:pool-1-thread-3执行任务:8
 2 线程:pool-1-thread-1执行任务:0
 3 线程:pool-1-thread-2执行任务:1
 4 线程:pool-1-thread-4执行任务:9
 5 线程:pool-1-thread-1执行任务:4
 6 线程:pool-1-thread-4执行任务:5
 7 线程:pool-1-thread-3执行任务:2
 8 线程:pool-1-thread-2执行任务:3
 9 线程:pool-1-thread-1执行任务:6
10 线程:pool-1-thread-4执行任务:7

当将任务数量增加到12个,则核心线程池2个,工作队列6个,再创建两个线程执行2个,还有2个任务将会被饱和策略直接拒绝,结果如下

 1 Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task [email protected]2 rejected from [email protected][Running, pool size = 4, active threads = 4, queued tasks = 6, completed tasks = 0]
 2     at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
 3     at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
 4     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
 5     at com.lucky.test.jvmtest.ThreadPoolTest.threadPoolTest1(ThreadPoolTest.java:32)
 6     at com.lucky.test.jvmtest.ThreadPoolTest.main(ThreadPoolTest.java:21)
 7 线程:pool-1-thread-4执行任务:9
 8 线程:pool-1-thread-1执行任务:0
 9 线程:pool-1-thread-3执行任务:8
10 线程:pool-1-thread-2执行任务:1
11 线程:pool-1-thread-2执行任务:5
12 线程:pool-1-thread-1执行任务:3
13 线程:pool-1-thread-3执行任务:4
14 线程:pool-1-thread-4执行任务:2
15 线程:pool-1-thread-1执行任务:7
16 线程:pool-1-thread-2执行任务:6

修改饱和策略,采用CallerRunsPolicy饱和策略,则其他十个任务正常执行,另外两个任务将会由提交任务的线程来执行任务,结果如下:

 1 线程:main执行任务:10
 2 线程:main执行任务:11
 3 线程:pool-1-thread-2执行任务:1
 4 线程:pool-1-thread-3执行任务:8
 5 线程:pool-1-thread-4执行任务:9
 6 线程:pool-1-thread-1执行任务:0
 7 线程:pool-1-thread-1执行任务:3
 8 线程:pool-1-thread-4执行任务:4
 9 线程:pool-1-thread-3执行任务:2
10 线程:pool-1-thread-2执行任务:5
11 线程:pool-1-thread-4执行任务:7
12 线程:pool-1-thread-1执行任务:6

总结:本文主要整理了线程池的基本知识点及大致用法,下篇将针对Executor框架的多种线程池分别整理

原文地址:https://www.cnblogs.com/jackion5/p/10663758.html

时间: 2024-10-07 09:33:17

深入浅出JAVA线程池使用原理1的相关文章

深入浅出JAVA线程池使用原理2

一.Executor框架介绍 Executor框架将Java多线程程序分解成若干个任务,将这些任务分配给若干个线程来处理,并得到任务的结果 1.1.Executor框架组成 任务:被执行任务需要实现的接口:Runnable接口或Callable接口 任务的执行:任务执行的核心接口Executor以及其子类ExecutorService接口 任务的结果:包括Future接口以及Future接口的实现类FutureTask类 Executor接口是Executor框架的基础,将任务的提交与执行分离开

Java 线程池的原理与实现

最近在学习线程池.内存控制等关于提高程序运行性能方面的编程技术,在网上看到有一哥们写得不错,故和大家一起分享. [分享]Java 线程池的原理与实现 这几天主要是狂看源程序,在弥补了一些以前知识空白的同时,也学会了不少新的知识(比如 NIO),或者称为新技术吧.线程池就是其中之一,一提到线程,我们会想到以前<操作系统>的生产者与消费者,信号量,同步控制等等.一提到池,我们会想到数据库连接池,但是线程池又如何呢? 建议:在阅读本文前,先理一理同步的知识,特别是syncronized同步关键字的用

Java线程池的原理及几类线程池的介绍

刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所花的时间以及系统资源的开销 如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及"过度切换". 线程池工作原理: 为什么要用线程池? 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到

Java 线程池的原理与实现 (转)

  最近在学习线程池.内存控制等关于提高程序运行性能方面的编程技术,在网上看到有一哥们写得不错,故和大家一起分享. [分享]Java 线程池的原理与实现 这几天主要是狂看源程序,在弥补了一些以前知识空白的同时,也学会了不少新的知识(比如 NIO),或者称为新技术吧.线程池就是其中之一,一提到线程,我们会想到以前<操作系统>的生产者与消费者,信号量,同步控制等等.一提到池,我们会想到数据库连接池,但是线程池又如何呢? 建议:在阅读本文前,先理一理同步的知识,特别是syncronized同步关键字

我眼中的java线程池实现原理

最近在看java线程池实现方面的源码,在此做个小结,因为网上关于线程池源码分析的博客挺多的,我也不打算重复造轮子啦,仅仅用纯语言描述的方式做做总结啦! 个人认为要想理解清楚java线程池实现原理,明白下面几个问题就可以了: (1):线程池存在哪些状态,这些状态之间是如何进行切换的呢? (2):线程池的种类有哪些? (3):创建线程池需要哪些参数,这些参数的具体含义是什么? (4):将任务添加到线程池之后运行流程? (5):线程池是怎么做到重用线程的呢? (6):线程池的关闭 首先回答第一个问题:

11 java 线程池 实现原理

一 关键类的实现 1 ThreadPoolExecutor类 java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类. 下面我们来看一下ThreadPoolExecutor类的具体实现源码. 在ThreadPoolExecutor类中提供了四个构造方法: 1 public class ThreadPoolExecutor extends AbstractExecutorService {

Java线程池实现原理与技术

本文将通过实现一个简易的线程池理解线程池的原理,以及介绍JDK中自带的线程池ThreadPoolExecutor和Executor框架. 1.无限制线程的缺陷 多线程的软件设计方法确实可以最大限度地发挥多核处理器的计算能力,提高生产系统的吞吐量和性能.但是,若不加控制和管理的随意使用线程,对系统的性能反而会产生不利的影响. 一种最为简单的线程创建和回收的方法类似如下: new Thread(new Runnable() { @Override public void run() { //do s

Java 线程池的原理与实现学习(一)

线程池:    多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.        假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间.        如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能.                一个线程池包括以下四个基本组成部分:                1.线程池管理器(ThreadPool):用于创建并管理线程

Java 线程池的原理与实现学习(二)

摘自:http://www.cnblogs.com/lxzh/archive/2013/01/20/2868736.html execute(Runnable command):履行Ruannable类型的任务 submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future对象 invokeAll(collection of tasks):执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表. shutdown():在完成已提