广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java多线程与线程池技术分享
  • 497
分享到

Java多线程与线程池技术分享

2024-04-02 19:04:59 497人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录一、序言1、普通执行2、线程池执行二、线程池基础1、核心参数2、参数与池的关系1、通用对比2、拓展对比3、无返回值任务4、有返回值任务三、Executors1、创建单一线程的线程

一、序言

Java多线程编程线程池被广泛使用,甚至成为了标配。

线程池本质是池化技术的应用,和连接池类似,创建连接与关闭连接属于耗时操作,创建线程与销毁线程也属于重操作,为了提高效率,先提前创建好一批线程,当有需要使用线程时从线程池取出,用完后放回线程池,这样避免了频繁创建与销毁线程。

// 任务
Runnable runnable = () -> System.out.println(Thread.currentThread().getId());

在应用中优先选用线程池执行异步任务,根据不同的场景选用不同的线程池,提高异步任务执行效率。

1、普通执行

new Thread(runnable).start();

2、线程池执行

Executors.newSingleThreadExecutor().execute(runnable)

二、线程池基础

1、核心参数

线程池的核心参数决定了池的类型,进而决定了池的特性。

参数解释行为
corePoolSize核心线程数池中长期维护的线程数量,不主动回收
maximumPoolSize最大线程数最大线程数大于等于核心线程数
keepAliveTime线程最大空闲时间非核心线程最大空闲时间,超时回收线程
workQueue工作队列工作队列直接决定线程池的类型

2、参数与池的关系

Executors类默认创建线程池与参数对应关系。

线程池corePoolSizemaximumPoolSizekeepAliveTimeworkQueue
newCachedThreadPool0Integer.MAX_VALUE60SynchronousQueue
newSingleThreadExecutor110LinkedBlockingQueue
newFixedThreadPoolNN0LinkedBlockingQueue
newScheduledThreadPoolNInteger.MAX_VALUE0DelayedWorkQueue

线程池对比:

根据使用场景选择对应的线程池。

1、通用对比

线程池特点适用场景
newCachedThreadPool超时未使用的线程回自动销毁,有新任务时自动创建适用于低频、轻量级的任务。回收线程的目的是节约线程长时间空闲而占有的资源。
newSingleThreadExecutor线程池中有且只有一个线程顺序执行任务
newFixedThreadPool线程池中有固定数量的线程,且一直存在适用于高频的任务,即线程在大多数时间里都处于工作状态。
newScheduledThreadPool定时线程池与定时调度相关联

2、拓展对比

维护仅有一个线程的线程池有如下两种方式,正常使用的情况下,二者差异不大;复杂使用环境下,二者存在细微的差异。用newSingleThreadExecutor方式创建的线程池在任何时刻至多只有一个线程,因此可以理解为用异步的方式执行顺序任务;后者初始化的时候也只有一个线程,使用过程中可能会出现最大线程数超过1的情况,这时要求线性执行的任务会并行执行,业务逻辑可能会出现问题,与实际场景有关。

private final static ExecutorService executor = Executors.newSingleThreadExecutor();
private final static ExecutorService executor = Executors.newFixedThreadPool(1);

线程池原理:

Java多线程与线程池技术_线程池

线程池主要处理流程,任务提交之后是怎么执行的。大致如下:

  • 判断核心线程池是否已满,如果不是,则创建线程执行任务
  • 如果核心线程池满了,判断队列是否满了,如果队列没满,将任务放在队列中
  • 如果队列满了,则判断线程池是否已满,如果没满,创建线程执行任务
  • 如果线程池也满了,则按照拒绝策略对任务进行处理

提交任务的方式:

往线程池中提交任务,主要有两种方法:提交无返回值的任务和提交有返回值的任务。

3、无返回值任务

execute用于提交不需要返回结果的任务。

public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.execute(() -> System.out.println("hello"));
}

4、有返回值任务

submit()用于提交一个需要返回果的任务。

该方法返回一个Future对象,通过调用这个对象的get()方法,我们就能获得返回结果。get()方法会一直阻塞,直到返回结果返回。

我们也可以使用它的重载方法get(long timeout, TimeUnit unit),这个方法也会阻塞,但是在超时时间内仍然没有返回结果时,将抛出异常TimeoutException

public static void main(String[] args) throws Exception {
    ExecutorService executor = Executors.newFixedThreadPool(2);
    Future<Long> future = executor.submit(() -> {
        System.out.println("task is executed");
        return System.currentTimeMillis();
    });
    System.out.println("task execute time is: " + future.get());
}

在提交任务时,如果无返回值任务,优先使用execute

关闭线程池:

在线程池使用完成之后,我们需要对线程池中的资源进行释放操作,这就涉及到关闭功能。我们可以调用线程池对象的shutdown()shutdownNow()方法来关闭线程池。

这两个方法都是关闭操作,又有什么不同呢?

  • shutdown()会将线程池状态置为SHUTDOWN,不再接受新的任务,同时会等待线程池中已有的任务执行完成再结束。
  • shutdownNow()会将线程池状态置为SHUTDOWN,对所有线程执行interrupt()操作,清空队列,并将队列中的任务返回回来。

另外,关闭线程池涉及到两个返回boolean的方法,isshutdown()isTerminated,分别表示是否关闭和是否终止。

三、Executors

Executors是一个线程池工厂,提供了很多的工厂方法,我们来看看它大概能创建哪些线程池。

// 创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor();
// 创建固定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads);
// 创建带缓存的线程池
public static ExecutorService newCachedThreadPool();
// 创建定时调度的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 创建流式(fork-join)线程池
public static ExecutorService newWorkStealingPool();

1、创建单一线程的线程池

任何时候线程池中至多只有一个线程,当线程执行异常终止时会自动创建一个新线程替换。如果既有异步执行任务的需求又希望任务得以顺序执行,那么此类型线程池是首选。

若多个任务被提交到此线程池,那么会被缓存到队列。当线程空闲的时候,按照FIFO的方式进行处理。

2、创建固定数量的线程池

创建核心线程与最大线程数相等的固定线程数的线程池,任何时刻至多有固定数目的线程,当线程因异常而终止时则会自动创建线程替换。

当有新任务加入时,如果池内线程均处于活跃状态,则任务进入等待队列中,直到有空闲线程,队列中的任务才会被顺序执行;如果池内有非活跃线程,则任务可以立刻得以执行。

  • 如果线程的数量未达到指定数量,则创建线程来执行任务
  • 如果线程池的数量达到了指定数量,并且有线程是空闲的,则取出空闲线程执行任务
  • 如果没有线程是空闲的,则将任务缓存到队列(队列长度为Integer.MAX_VALUE)。当线程空闲的时候,按照FIFO的方式进行处理

3、创建可伸缩的线程池

这种方式创建的线程池,核心线程池的长度为0,线程池最大长度为Integer.MAX_VALUE。由于本身使用SynchronousQueue作为等待队列的缘故,导致往队列里面每插入一个元素,必须等待另一个线程从这个队列删除一个元素。

  • 线程池可维护0到Integer.MAX_VALUE个线程资源,空闲线程默认情况下超过60秒未使用则会被销毁,长期闲置的池占用较少的资源。
  • 当有新任务加入时,如果池中有空闲且尚未销毁的线程,则将任务交给此线程执行;如果没有可用的线程,则创建一个新线程执行任务并添加到池中。

4、创建定时调度的线程池

和上面3个工厂方法返回的线程池类型有所不同,它返回的是ScheduledThreadPoolExecutor类型的线程池。平时我们实现定时调度功能的时候,可能更多的是使用第三方类库,比如:quartz等。但是对于更底层的功能,我们仍然需要了解。

四、手动创建线程池

理论上,我们可以通过Executors来创建线程池,这种方式非常简单。但正是因为简单,所以限制了线程池的功能。比如:无长度限制的队列,可能因为任务堆积导致OOM,这是非常严重的bug,应尽可能地避免。怎么避免?归根结底,还是需要我们通过更底层的方式来创建线程池。

抛开定时调度的线程池不管,我们看看ThreadPoolExecutor。它提供了好几个构造方法,但是最底层的构造方法却只有一个。那么,我们就从这个构造方法着手分析。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler);

这个构造方法有7个参数,我们逐一来进行分析。

  • corePoolSize,线程池中的核心线程数
  • maximumPoolSize,线程池中的最大线程数
  • keepAliveTime,空闲时间,当线程池数量超过核心线程数时,多余的空闲线程存活的时间,即:这些线程多久被销毁。
  • unit,空闲时间的单位,可以是毫秒、秒、分钟、小时和天,等等
  • workQueue,等待队列,线程池中的线程数超过核心线程数时,任务将放在等待队列,它是一个BlockingQueue类型的对象
  • threadFactory,线程工厂,我们可以使用它来创建一个线程
  • handler,拒绝策略,当线程池和等待队列都满了之后,需要通过该对象的回调函数进行回调处理

这些参数里面,基本类型的参数都比较简单,我们不做进一步的分析。我们更关心的是workQueuethreadFactoryhandler,接下来我们将进一步分析。

等待队列-workQueue

等待队列是BlockingQueue类型的,理论上只要是它的子类,我们都可以用来作为等待队列。

同时,jdk内部自带一些阻塞队列,我们来看看大概有哪些。

  • ArrayBlockingQueue,队列是有界的,基于数组实现的阻塞队列
  • LinkedBlockingQueue,队列可以有界,也可以无界。基于链表实现的阻塞队列
  • SynchronousQueue,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。该队列也是Executors.newCachedThreadPool()的默认队列
  • PriorityBlockingQueue,带优先级的无界阻塞队列

通常情况下,我们需要指定阻塞队列的上界(比如1024)。另外,如果执行的任务很多,我们可能需要将任务进行分类,然后将不同分类的任务放到不同的线程池中执行。

线程工厂-threadFactory

ThreadFactory是一个接口,只有一个方法。既然是线程工厂,那么我们就可以用它生产一个线程对象。来看看这个接口的定义。

public interface ThreadFactory {

    
    Thread newThread(Runnable r);
}

Executors的实现使用了默认的线程工厂-DefaultThreadFactory。它的实现主要用于创建一个线程,线程的名字为pool-{poolNum}-thread-{threadNum}

static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

很多时候,我们需要自定义线程名字。我们只需要自己实现ThreadFactory,用于创建特定场景的线程即可。

拒绝策略-handler

所谓拒绝策略,就是当线程池满了、队列也满了的时候,我们对任务采取的措施。或者丢弃、或者执行、或者其他…

jdk自带4种拒绝策略,我们来看看。

  • CallerRunsPolicy // 在调用者线程执行
  • AbortPolicy // 直接抛出RejectedExecutionException异常
  • DiscardPolicy // 任务直接丢弃,不做任何处理
  • DiscardOldestPolicy // 丢弃队列里最旧的那个任务,再尝试执行当前任务

这四种策略各有优劣,比较常用的是DiscardPolicy,但是这种策略有一个弊端就是任务执行的轨迹不会被记录下来。所以,我们往往需要实现自定义的拒绝策略, 通过实现RejectedExecutionHandler接口的方式。

五、其它

1、配置线程池的参数

前面我们讲到了手动创建线程池涉及到的几个参数,那么我们要如何设置这些参数才算是正确的应用呢?实际上,需要根据任务的特性来分析。

  • 任务的性质:CPU密集型、IO密集型和混杂型
  • 任务的优先级:高中低
  • 任务执行的时间:长中短
  • 任务的依赖性:是否依赖数据库或者其他系统资源

不同的性质的任务,我们采取的配置将有所不同。在《Java并发编程实践》中有相应的计算公式。

通常来说,如果任务属于CPU密集型,那么我们可以将线程池数量设置成CPU的个数,以减少线程切换带来的开销。如果任务属于IO密集型,我们可以将线程池数量设置得更多一些,比如CPU个数*2。

PS:我们可以通过Runtime.getRuntime().availableProcessors()来获取CPU的个数。

2、线程池监控

如果系统中大量用到了线程池,那么我们有必要对线程池进行监控。利用监控,我们能在问题出现前提前感知到,也可以根据监控信息来定位可能出现的问题。

那么我们可以监控哪些信息?又有哪些方法可用于我们的扩展支持呢?

首先,ThreadPoolExecutor自带了一些方法。

  • long getTaskCount(),获取已经执行或正在执行的任务数
  • long getCompletedTaskCount(),获取已经执行的任务数
  • int getLargestPoolSize(),获取线程池曾经创建过的最大线程数,根据这个参数,我们可以知道线程池是否满过
  • int getPoolSize(),获取线程池线程数
  • int getActiveCount(),获取活跃线程数(正在执行任务的线程数)

其次,ThreadPoolExecutor留给我们自行处理的方法有3个,它在ThreadPoolExecutor中为空实现(也就是什么都不做)。

  • protected void beforeExecute(Thread t, Runnable r) // 任务执行前被调用
  • protected void afterExecute(Runnable r, Throwable t) // 任务执行后被调用
  • protected void terminated() // 线程池结束后被调用

六、总结

  • 尽量使用手动的方式创建线程池,避免使用Executors工厂类
  • 根据场景,合理设置线程池的各个参数,包括线程池数量、队列、线程工厂和拒绝策略

 到此这篇关于Java十分钟入门多线程下篇的文章就介绍到这了,更多相关Java 多线程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java多线程与线程池技术分享

本文链接: https://www.lsjlt.com/news/142042.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java多线程与线程池技术分享
    目录一、序言1、普通执行2、线程池执行二、线程池基础1、核心参数2、参数与池的关系1、通用对比2、拓展对比3、无返回值任务4、有返回值任务三、Executors1、创建单一线程的线程...
    99+
    2022-11-13
  • Java多线程案例之线程池
    文章目录 一. 线程池概述1. 什么是线程池2. Java标准库提供的线程池 二. 线程池的简单实现 一. 线程池概述 1. 什么是线程池 线程池和和字符串常量池, 数据库连接池一样,...
    99+
    2023-09-04
    java 线程池 多线程
  • java中多线程与线程池的基本使用方法
    目录前言继承Thread 实现Runnale接口Callable线程池常见的4种线程池。总结前言 在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器...
    99+
    2022-11-12
  • 从java源码分析线程池(池化技术)的实现原理
    目录线程池的起源线程池的定义和使用方案一:Executors(仅做了解,推荐使用方案二)方案二:ThreadPoolExecutor线程池的实现原理前言: 线程池是一个非常重要的知识...
    99+
    2022-11-13
  • 技术分享 | 从库 MTS 多线程并行回放(一)
    本节包含分发调用流程请参考链接: https://www.jianshu.com/p/8706d7422d89 一、综述 与单 SQL 线程的回放不同,MTS 包含多个工作线程,原有的 SQL 线程蜕变为协调线程。SQL 协调线程同时还承...
    99+
    2014-09-28
    技术分享 | 从库 MTS 多线程并行回放(一)
  • 技术分享 | 从库 MTS 多线程并行回放(二)
    本节包含一个笔记如下: https://www.jianshu.com/p/e920a6d33005 这一节会先描述 MTS 的工作线程执行 Event 的大概流程。然后重点描述一下 MTS 中检查点的概念。在后面的第 25 节我们可以看...
    99+
    2020-09-11
    技术分享 | 从库 MTS 多线程并行回放(二)
  • Java多线程 自定义线程池详情
    主要介绍: 1.任务队列 2.拒绝策略(抛出异常、直接丢弃、阻塞、临时队列) 3.init( min ) 4.active 5.max ...
    99+
    2022-11-12
  • Java线程池的优点及池化技术的应用
    目录1.池化技术2.池化技术应用2.1 线程池2.2 内存池2.3 数据库连接池2.4 HttpClient连接池3.线程池介绍4.线程池优点分析优点1:复用线程,降低资源消耗优点2...
    99+
    2022-11-13
  • Java多线程之线程池七个参数的示例分析
    这篇文章主要介绍Java多线程之线程池七个参数的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交...
    99+
    2023-06-14
  • java怎么使用线程池启动多线程
    在 Java 中,可以使用线程池来启动多线程。以下是使用线程池启动多线程的示例代码:首先,需要导入 `java.util.concu...
    99+
    2023-09-15
    java
  • 怎么理解ThreadPoolExecutor线程池技术
    本篇文章为大家展示了怎么理解ThreadPoolExecutor线程池技术,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java是一门多线程的语言,基本上生产环境的Java项目都离不开多线程。而线程...
    99+
    2023-06-19
  • Java多线程常见案例分析线程池与单例模式及阻塞队列
    目录一、单例模式1、饿汉模式2、懒汉模式(单线程)3、懒汉模式(多线程)二、阻塞队列阻塞队列的实现生产者消费者模型三、线程池1、创建线程池的的方法(1)ThreadPoolExecu...
    99+
    2022-11-13
  • Java线程池中多余的线程怎么回收
    这篇文章给大家分享的是有关Java线程池中多余的线程怎么回收的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。那么,就以JDK1.8为例分析吧。1.runWorker(Worker w)工作线程启动后,就进入runW...
    99+
    2023-06-15
  • Java多线程之线程池七个参数详解
    目录corePoolSize:核心线程数maximumPoolSize:最大线程数keepAliveTime:空闲线程存活时间unit:时间单位workQueue:工作队列threa...
    99+
    2022-11-12
  • 一文彻底搞懂java多线程和线程池
    目录 什么是线程 一. Java实现线程的三种方式1.1、继承Thread类1.2、实现Runnable接口,并覆写run方法二. Callable接口...
    99+
    2022-11-12
  • 深入理解Java多线程与并发框(第⑪篇)——线程池参数
    ThreadPoolExecutor线程池线程的创建和销毁都会消耗大量资源,就好像公司每天上午9点工作时就招进一批员工,晚上6点干完活就辞退一批员工,这都会销毁公司大量资源。所以合理利用 “池” 中固定、稳定的线程是非常有必要的。扩展关系T...
    99+
    2023-06-05
  • 微服务 第一章 Java线程池技术应用
    系列文章目录 第一章 Java线程池技术应用 文章目录 系列文章目录@[TOC](文章目录) 前言1、Java创建线程方式回顾1.1、继承Thread类(只运行一次)1.1.1...
    99+
    2023-09-16
    微服务 java python 原力计划
  • Java多线程并发、并行、线程与进程实例分析
    本篇内容介绍了“Java多线程并发、并行、线程与进程实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、并发与并行并发:指两个或多个事...
    99+
    2023-07-02
  • Java多线程 - 创建线程池的方法 - ThreadPoolExecutor和Executors
    文章目录 线程池(重点)线程池介绍实现线程池的方式方式一: 实现类ThreadPoolExecutorThreadPoolExecutor构造器的参数线程池处理Runnable任务线程池处理Callable任务 方式二: ...
    99+
    2023-08-30
    java jvm 开发语言
  • android 多线程技术应用
    多线程案例——计时器 这个案例中,屏幕启动之后,进入如图所示的界面。 屏幕上有一个文本框用于显示逝去的时间,此外还有一个“停止计时”按钮。案例的用例图如图所示。  能...
    99+
    2022-06-06
    技术应用 多线程 线程 Android
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作