返回顶部
首页 > 资讯 > 后端开发 > Python >彻底搞懂java并发ThreadPoolExecutor使用
  • 650
分享到

彻底搞懂java并发ThreadPoolExecutor使用

java并发ThreadPoolExecutorjava并发 2023-02-28 17:02:57 650人浏览 独家记忆

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

摘要

目录前言正文一. 线程池的简单原理二. 线程池的创建三. 线程池执行任务1. 执行无返回值任务2. 执行有返回值任务3. 执行有返回值任务时抛出错误4. ThreadPoolExec

前言

线程池是Java中使用较多的并发框架,合理使用线程池,可以:降低资源消耗提高响应速度提高线程的可管理性

本篇文章将从线程池简单原理线程池的创建线程池执行任务关闭线程池进行使用学习

正文

一. 线程池的简单原理

当一个任务提交到线程池ThreadPoolExecutor时,该任务的执行如下图所示。

  • 如果当前运行的线程数小于corePoolSzie(核心线程数),则创建新线程来执行任务(需要获取全局);
  • 如果当前运行的线程数等于或大于corePoolSzie,则将任务加入BlockingQueue(任务阻塞队列);
  • 如果BlockingQueue已满,则创建新的线程来执行任务(需要获取全局锁);
  • 如果创建新线程会使当前线程数大于maximumPoolSize(最大线程数),则拒绝任务并调用RejectedExecutionHandlerrejectedExecution() 方法。

由于ThreadPoolExecutor存储工作线程使用的集合HashSet,因此执行上述步骤1和步骤3时需要获取全局锁来保证线程安全,而获取全局锁会导致线程池性能瓶颈,因此通常情况下,线程池完成预热后(当前线程数大于等于corePoolSize),线程池的execute() 方法都是执行步骤2。

二. 线程池的创建

通过ThreadPoolExecutor能够创建一个线程池,ThreadPoolExecutor的构造函数签名如下。

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

通过ThreadPoolExecutor创建线程池时,需要指定线程池的核心线程数最大线程数线程保活时间线程保活时间单位任务阻塞队列,并按需指定线程工厂饱和拒绝策略,如果不指定线程工厂饱和拒绝策略,则ThreadPoolExecutor会使用默认的线程工厂饱和拒绝策略。下面分别介绍这些参数的含义。

参数含义
corePoolSize核心线程数,即线程池的基本大小。当一个任务被提交到线程池时,如果线程池的线程数小于corePoolSize,那么无论其余线程是否空闲,也需创建一个新线程来执行任务。
maximumPoolSize最大线程数。当线程池中线程数大于等于corePoolSize时,新提交的任务会加入任务阻塞队列,但是如果任务阻塞队列已满且线程数小于maximumPoolSize,此时会继续创建新的线程来执行任务。该参数规定了线程池允许创建的最大线程数
keepAliveTime线程保活时间。当线程池的线程数大于核心线程数时,多余的空闲线程会最大存活keepAliveTime的时间,如果超过这个时间且空闲线程还没有获取到任务来执行,则该空闲线程会被回收掉。
unit线程保活时间单位。通过TimeUnit指定线程保活时间的时间单位,可选单位有DAYS(天),HOURS(时),MINUTES(分),SECONDS(秒),MILLISECONDS(毫秒),MICROSECONDS(微秒)和NANOSECONDS(纳秒),但无论指定什么时间单位,ThreadPoolExecutor统一会将其转换为NANOSECONDS
workQueue任务阻塞队列。线程池的线程数大于等于corePoolSize时,新提交的任务会添加到workQueue中,所有线程执行完上一个任务后,会循环从workQueue中获取任务来执行。
threadFactory创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
handler饱和拒绝策略。如果任务阻塞队列已满且线程池中的线程数等于maximumPoolSize,说明线程池此时处于饱和状态,应该执行一种拒绝策略来处理新提交的任务。

三. 线程池执行任务

1. 执行无返回值任务

通过ThreadPoolExecutorexecute() 方法,能执行Runnable任务,示例如下。

public class ThreadPoolExecutorTest {
    @Test
    public void ThreadPoolExecutor执行简单无返回值任务() throws Exception {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue&lt;&gt;(300));
        // 创建两个任务
        Runnable firstRunnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("第一个任务执行");
            }
        };
        Runnable secondRunnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("第二个任务执行");
            }
        };
        // 让线程池执行任务
        threadPoolExecutor.execute(firstRunnable);
        threadPoolExecutor.execute(secondRunnable);
        // 让主线程睡眠1秒,等待线程池中的任务被执行完毕
        Thread.sleep(1000);
    }
}

运行测试程序,结果如下。

2. 执行有返回值任务

通过ThreadPoolExecutorsubmit() 方法,能够执行Callable任务,通过submit() 方法返回的RunnableFuture能够拿到异步执行的结果。示例如下。

public class ThreadPoolExecutorTest {
    @Test
    public void ThreadPoolExecutor执行简单有返回值任务() throws Exception {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue&lt;&gt;(300));
        // 创建两个任务,任务执行完有返回值
        Callable&lt;String&gt; firstCallable = new Callable&lt;String&gt;() {
            @Override
            public String call() throws Exception {
                return "第一个任务返回值";
            }
        };
        Callable&lt;String&gt; secondCallable = new Callable&lt;String&gt;() {
            @Override
            public String call() throws Exception {
                return "第二个任务返回值";
            }
        };
        // 让线程池执行任务
        Future&lt;String&gt; firstFuture = threadPoolExecutor.submit(firstCallable);
        Future&lt;String&gt; secondFuture = threadPoolExecutor.submit(secondCallable);
        // 获取执行结果,拿不到结果会阻塞在get()方法上
        System.out.println(firstFuture.get());
        System.out.println(secondFuture.get());
    }
}

运行测试程序,结果如下。

3. 执行有返回值任务时抛出错误

如果ThreadPoolExecutor在执行Callable任务时,在Callable任务中抛出了异常并且没有捕获,那么这个异常是可以通过Futureget() 方法感知到的。示例如下。

public class ThreadPoolExecutorTest {
    @Test
    public void ThreadPoolExecutor执行简单有返回值任务时抛出错误() {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
        // 创建一个任务,任务有返回值,但是执行过程中抛出异常
        Callable<String> exceptionCallable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                throw new RuntimeException("发生了异常");
            }
        };
        // 让线程池执行任务
        Future<String> exceptionFuture = threadPoolExecutor.submit(exceptionCallable);
        try {
            System.out.println(exceptionFuture.get());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

运行测试程序,结果如下。

4. ThreadPoolExecutor通过submit方式执行Runnable

ThreadPoolExecutor可以通过submit() 方法来运行Runnable任务,并且还可以异步获取执行结果。示例如下。

public class ThreadPoolExecutorTest {
    @Test
    public void ThreadPoolExecutor通过submit的方式来提交并执行Runnable() throws Exception {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue&lt;&gt;(300));
        // 创建结果对象
        MyResult myResult = new MyResult();
        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myResult.setResult("任务执行了");
            }
        };
        // 通过ThreadPoolExecutor的submit()方法提交Runnable
        Future&lt;MyResult&gt; resultFuture = threadPoolExecutor.submit(runnable, myResult);
        // 获取执行结果
        MyResult finalResult = resultFuture.get();
        // myResult和finalResult的地址实际相同
        Assert.assertEquals(myResult, finalResult);
        // 打印执行结果
        System.out.println(resultFuture.get().getResult());
    }
    private static class MyResult {
        String result;
        public MyResult() {}
        public MyResult(String result) {
            this.result = result;
        }
        public String getResult() {
            return result;
        }
        public void setResult(String result) {
            this.result = result;
        }
    }
}

运行测试程序,结果如下。

实际上ThreadPoolExecutorsubmit() 方法无论是提交Runnable任务还是Callable任务,都是将任务封装成了RunnableFuture接口的子类FutureTask,然后调用ThreadPoolExecutorexecute() 方法来执行FutureTask

四. 关闭线程池

关闭线程池可以通过ThreadPoolExecutorshutdown() 方法,但是shutdown() 方法不会去中断正在执行任务的线程,所以如果线程池里有Worker正在执行一个永远不会结束的任务,那么shutdown() 方法是无法关闭线程池的。示例如下。

public class ThreadPoolExecutorTest {
    @Test
    public void 通过shutdown关闭线程池() {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    LockSupport.parkNanos(1000 * 1000 * 1000);
                }
                System.out.println(Thread.currentThread().getName() + " 被中断");
            }
        };
        // 让线程池执行任务
        threadPoolExecutor.execute(runnable);
        threadPoolExecutor.execute(runnable);
        // 调用shutdown方法关闭线程池
        threadPoolExecutor.shutdown();
        // 等待3秒观察现象
        LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
    }
}

运行测试程序,会发现在主线程中等待3秒后,也没有得到预期的打印结果。如果上述测试程序中使用shutdownNow,则是可以得到预期打印结果的,示例如下。

public class ThreadPoolExecutorTest {
    @Test
    public void 通过shutdownNow关闭线程池() {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    LockSupport.parkNanos(1000 * 1000 * 1000);
                }
                System.out.println(Thread.currentThread().getName() + " 被中断");
            }
        };
        // 让线程池执行任务
        threadPoolExecutor.execute(runnable);
        threadPoolExecutor.execute(runnable);
        // 调用shutdown方法关闭线程池
        threadPoolExecutor.shutdownNow();
        // 等待3秒观察现象
        LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
    }
}

运行测试程序,打印如下。

因为测试程序中的任务是响应中断的,而ThreadPoolExecutorshutdownNow() 方法会中断所有Worker,所以执行shutdownNow() 方法后,正在运行的任务会响应中断并结束运行,最终线程池关闭。

假如线程池中运行着一个永远不会结束的任务,且这个任务不响应中断,那么无论是shutdown() 方法还是shutdownNow() 方法,都是无法关闭线程池的。

总结

ThreadPoolExecutor的使用总结如下。

  • 通过ThreadPoolExecutorexecute() 方法能够执行Runnable任务;
  • 通过ThreadPoolExecutorsubmit() 方法能够执行Runnable任务和Callable任务,并且能够获取异步的执行结果;
  • ThreadPoolExecutorsubmit() 方法会返回一个Future对象(实际就是FutureTask),如果任务执行过程中发生了异常且未捕获,那么可以通过Futureget() 方法感知到异常;
  • ThreadPoolExecutorsubmit() 方法无论是提交Runnable任务还是Callable任务,都是将任务封装成了RunnableFuture接口的子类FutureTask,然后调用ThreadPoolExecutorexecute() 方法来执行FutureTask
  • 关闭线程池时,如果运行的任务可以在有限时间内运行完毕,那么可以使用shutdown() 方法来关闭线程池,这能够保证在关闭线程池时,正在运行的任务会顺利运行完毕;
  • 关闭线程池时,如果运行的任务永远不会结束但是响应中断,那么可以使用shutdownNow() 方法来关闭线程池,这种方式不保证任务顺利运行完毕;
  • 如果任务永远不会结束且不响应中断,那么无论是shutdown() 方法还是shutdownNow() 方法,都无法关闭线程池。

以上就是彻底搞懂java并发ThreadPoolExecutor使用的详细内容,更多关于java并发ThreadPoolExecutor的资料请关注编程网其它相关文章!

--结束END--

本文标题: 彻底搞懂java并发ThreadPoolExecutor使用

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

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

猜你喜欢
  • 彻底搞懂java并发ThreadPoolExecutor使用
    目录前言正文一. 线程池的简单原理二. 线程池的创建三. 线程池执行任务1. 执行无返回值任务2. 执行有返回值任务3. 执行有返回值任务时抛出错误4. ThreadPoolExec...
    99+
    2023-02-28
    java并发ThreadPoolExecutor java 并发
  • 彻底搞懂Java多线程(一)
    目录Java多线程线程的创建线程常用方法线程的终止1.自定义实现线程的终止2.使用Thread的interrupted来中断3.Thraed.interrupted()方法和Thre...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(二)
    目录Java中的锁1.synchronized锁(jvm层的解决方案,也叫监视器锁)2.手动锁Locksynchronized锁synchronized使用场景1.使用synchro...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(三)
    目录Java线程池线程池的优点线程池的6种创建方式创建单个线程池的作用是什么?线程池的第七种创建方式ThreadPoolExecutor的执行方式ThreadPoolExecutor...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(四)
    目录SimpleDateFormat非线程安全问题ThreadLocalThreadLocal的原理ThreadLocal常用方法ThreadLocal的初始化Inheritable...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(五)
    目录单例模式与多线程立即加载/饿汉模式延时加载/懒汉模式饿汉/懒汉对比阻塞队列的实现常见的锁策略乐观锁CASCAS在java中的应用CAS 的ABA问题ABA 问题的解决悲观锁独占锁...
    99+
    2024-04-02
  • Java基础:彻底搞懂java多线程
    目录进程与线程使用多线程的优势线程的状态创建线程线程中断总结进程与线程 进程 进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可...
    99+
    2024-04-02
  • Java基础之让你彻底搞懂代理模式
    目录一、代理模式二、静态代理三、动态代理四、总结一、代理模式 什么是代理模式? 先来生活常用例子:你想买票,你没必要去车站买;而是可以去一个代售点,代售点代理车站卖票,这就是一个简单...
    99+
    2024-04-02
  • 一文彻底搞懂java多线程和线程池
    目录 什么是线程 一. Java实现线程的三种方式1.1、继承Thread类1.2、实现Runnable接口,并覆写run方法二. Callable接口...
    99+
    2024-04-02
  • java并发ThreadPoolExecutor如何使用
    这篇文章主要介绍“java并发ThreadPoolExecutor如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java并发ThreadPoolExecutor如何使用”文章能帮助大家解决问...
    99+
    2023-07-05
  • Java并发之搞懂读写锁
    目录ReentrantReadWriteLock小结StampedLock小结总结ReentrantReadWriteLock 我们来探讨一下java.concurrent.util...
    99+
    2024-04-02
  • 一文彻底搞懂Java和JDK的版本命名问题
    Java是面向对象的编程语言,在我们开发Java应用的程序员的专业术语里,Java这个单词其实指的是Java开发工具,也就是JDK(Java Development Kit)。所以我...
    99+
    2024-04-02
  • 一文带你彻底搞懂Docker中的cgroup的具体使用
    目录什么是cgroupcgroup的组成cgroup提供的功能限制cgroup中的CPU限制cgroup中的内存限制cgoup的进程数前言 进程在系统中使用CPU、内存、磁盘等计算资...
    99+
    2024-04-02
  • Java并发中如何搞懂读写锁
    本篇文章为大家展示了Java并发中如何搞懂读写锁,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。ReentrantReadWriteLock我们来探讨一下java.concurrent.util包下的...
    99+
    2023-06-25
  • 一篇文章彻底搞懂Python类属性和方法的调用
    目录一、类、对象概述二、类的定义与使用三、类属性和类方法的调用四、私有成员与公有成员总结Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象...
    99+
    2024-04-02
  • 10分钟彻底搞懂微信小程序单页面应用路由
    单页面应用特征 「假设:」 在一个 web 页面中,有1个按钮,点击可跳转到站内其他页面。 「多页面应用:」 点击按钮,会从新加载一个html资源,刷新整个页面; 「单页面应用:」...
    99+
    2024-04-02
  • java ThreadPoolExecutor 并发调用实例详解
    java ThreadPoolExecutor 并发调用实例详解概述通常为了提供任务的处理速度,会使用一些并发模型,ThreadPoolExecutor中的invokeAll便是一种。代码package test.current;impor...
    99+
    2023-05-31
    java threadpoolexecutor 并发调用
  • 一文搞懂Java并发AQS的共享锁模式
    目录概述自定义共享锁例子核心原理机制源码解析成员变量共享锁获取acquireShared(int)共享释放releaseShared(int)概述 这篇文章深入浅出理解Java并发A...
    99+
    2022-11-13
    Java AQS共享锁模式 Java AQS共享锁 Java AQS
  • 一文搞懂Java ScheduledExecutorService的使用
    目录一、创建ScheduledExecutorService对象二、ScheduledExecutorService方法三、固定速率和固定延时的区别1. 固定速率2. 固定延时四、调...
    99+
    2022-11-13
    Java ScheduledExecutorService使用 Java ScheduledExecutorService
  • cookie是什么?有什么用?cookie详解,一篇文章彻底搞懂cookie
    Cookie是什么         cookie的中文翻译是曲奇,小甜饼的意思。cookie其实就是一些数据信息,类型为“小型文本文件”,存储于电脑上的文本文件中。 Cookie有什么用         我们想象一个场景,当我们打开一个网...
    99+
    2023-08-31
    java 服务器 后端 网络协议
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作