Java多线程技术10——线程池ThreadPoolExecutor之Executor接口

news2024/11/27 18:45:51

1 概述

        在开发服务器软件项目时,经常需要处理执行时间很短并且数据巨大的请求,如果为每一个请求创建一个新的线程,则会导致性能上的瓶颈。因为JVM需要频繁地处理线程对象的创建和销毁,如果请求的执行时间很短,则有可能花在创建和销毁线程对象上的时间大于真正执行任务的时间,导致系统性能会大幅降低。

        JDK5及以上版本提供了对线程池的支持,主要用于支持高并发的访问处理,并且复用线程对象,线程池核心原理是创建一个“线程池(ThreadPool)”,在池中堆线程对象进行管理,包括创建与销毁,使用池时只需要执行具体的任务即可,线程对象的处理都在池中被封装了。

        线程池类ThreadPoolExecutor实现了Executors接口,该接口是学习线程池的重点,因为掌握了该接口中的方法也就大概掌握了ThreadPoolExecutor类的主要功能。

2 Executor接口介绍

        Executor接口结构非常简单,仅有一个方法。

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

        但Executor是接口,不能直接使用,所以还需要实现类。ExecutorService接口是Executor子接口,在内部添加了比较多的方法。

        虽然ExecutorService接口添加了若干个方法的定义,但还是不能实例化,那么就要看看它的唯一子实现类AbstractExecutorService。 

        由于AbstractExecutorService是抽象类,所以同样不能实例化。再来看一下AbstractExecutorService类的子类ThreadPoolExecutor类。

public class ThreadPoolExecutor extends AbstractExecutorService {}

    ThreadPoolExecutor类的方法列表如下:

    

注:方法较多,截图仅为一部分。

3 使用Executors工厂类创建线程池

        Executor接口仅仅是一种规范、一种声明、一种定义,并没有实现任何的功能,所以大多数情况下,需要使用接口的实现类来完成指定的功能。比如ThreadPoolExecutor类就是Executor的实现类,但ThreadPoolExecutor类在使用上并不方便,在实例化时需要传入多个参数,还要考虑线程的并发数等与线程池运行效率相关的参数,所以官方建议使用Executors工厂类来创建线程池对象,该类对创建ThreadPoolExecutor线程池进行封装,直接调用即可。

        Executors类中的方法如下图:

4 使用newCachedThreadPool()方法创建无界线程池

        使用newCachedThreadPool()方法创建无界线程池,可以进行线程自动回收。所谓“无界线程池”就是池中存放线程个数是理论上的最大值,即Integer.MAX_VALUE。

public class Run1 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Runnable1 begin "+System.currentTimeMillis());
                    Thread.sleep(1000);
                    System.out.println("A");
                    System.out.println("Runnable1 end" + System.currentTimeMillis());
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Runnable2 begin "+System.currentTimeMillis());
                    Thread.sleep(1000);
                    System.out.println("B");
                    System.out.println("Runnable2 end" + System.currentTimeMillis());
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
    }


}

        从打印时间来看,A和B几乎是在相同的时间开始打印的,也就是创建了2个线程,而且2个线程之间是异步运行的。

public class Run2 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("run");
                }
            });
        }
    }
}

 5 验证newCachedThreadPool()方法创建线程池和线程复用特性

        前面的实验没有验证newCachedThreadPool ()方法创建的是线程池,下面会验证。

public class MyRunnable implements Runnable{
    private String username;

    public MyRunnable(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + "username = " + username +
                    " begin "+System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "username = " + username +
                    " end "+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            service.execute(new MyRunnable((""+(i+1))));
        }
    }
}

         

        通过控制台可以看到,线程池对象创建是完全成功的,但还没有达到池中线程对象可以复用的效果,下面的实验要实现这样的效果。

public class MyRunnable1 implements Runnable{
    private String username;

    public MyRunnable1(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "username = " + username +
                " begin "+System.currentTimeMillis());
        System.out.println(Thread.currentThread().getName() + "username = " + username +
                " end "+System.currentTimeMillis());
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            service.execute(new MyRunnable1((""+(i+1))));
        }
        Thread.sleep(1000);
        System.out.println("");
        System.out.println("");
        for (int i = 0; i < 5; i++) {
            service.execute(new MyRunnable1((""+(i+1))));
        }
    }
}

 6 使用newCachedThreadPool()定制线程工厂

        

public class MyThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName("定制池中的线程对象名称:"+Math.random());
        return thread;
    }
}
public class Run {
    public static void main(String[] args) {
        MyThreadFactory factory = new MyThreadFactory();
        ExecutorService service = Executors.newCachedThreadPool(factory);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("运行"+System.currentTimeMillis()+ " " + Thread.currentThread().getName());
            }
        });
    }
}

        通过使用自定义的ThreadFactory接口实现类,实现了线程对象的定制性。 ThreadPoolExecutor、ThreadFactory和Thread之间的关系是ThreadPoolExecutor类使用了ThreadFactory方法来创建Thread对象。内部源代码如下:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

        在源码中使用了默认线程工厂,源代码如下:

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

7 使用newCachedThreadPool方法创建无边界线程池的缺点

        如果在高并发的情况下,使用newCachedThreadPool()方法创建无边界线程池极易造成内存占用率大幅升高,导致内存溢出或者系统运行效率严重下降。

public class Run1 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 200000; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("runnable begin " + Thread.currentThread().getName()
                        + " " + System.currentTimeMillis());;
                        Thread.sleep(1000*60*5);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

        

        程序运行后再“任务管理器”中查看可用内存极速下降,系统运行效率大幅降低,超大的内存空间都被Thread类对象占用了,无界线程池对线程的数量没有控制,这时可以尝试使用有界线程池来限制线程池占用内存的最大空间。

8 使用newFixedThreadPool(int)方法创建有界线程池

public class MyRunnable implements Runnable{
    private String username;

    public MyRunnable(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " username = " + username + " begin " + System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " username = " + username + " end " + System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 3; i++) {
            service.execute(new MyRunnable(("" + (i + 1))));
        }
        for (int i = 0; i < 3; i++) {
            service.execute(new MyRunnable(("" + (i + 1))));
        }

    }
}

 

        通过控制台可以看到,使用有界线程池后线程池中最多的线程个数是可控的。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1357109.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【linux】日志管理和分析

一、概述 在Linux系统的管理和运维中&#xff0c;日志文件起到至关重要的作用。它们记录了系统运行过程中的各种事件&#xff0c;包括系统故障、性能数据和安全事件。 二、 日志的作用和分类 日志的作用 日志文件记载了系统的生命线&#xff0c;利用它们可以&#xff1a; 1…

Linux第7步_设置虚拟机的电源

设置ubuntu代码下载源和关闭“自动检查更新”后&#xff0c;就要学习设置“虚拟机的电源”了。 用处不大&#xff0c;主要是了解”螺丝刀和扳手形状的图标“在哪里。 1、打开虚拟机&#xff0c;点击最右边的“下拉按钮”&#xff0c;弹出对话框&#xff0c;得到下图&#xff…

【算法】算法设计与分析 期末复习总结

第一章 算法概述 时间复杂度比大小&#xff0c;用代入法&#xff0c;代入2即可。求渐进表达式&#xff0c;就是求极限&#xff0c;以极限为O的括号&#xff1b;O是指上界&#xff0c;Ω是指下界&#xff0c;θ是指上下界相等&#xff0c;在这里&#xff0c;可以这样理解&#…

覆盖与交换-第四十二天

目录 内存空间的扩充 覆盖技术 实例 交换技术 思考 本节思维导图 内存空间的扩充 历史背景&#xff1a;早期计算机内存很小&#xff0c;内存大小不够的情况解决办法&#xff1a;后来人们引入了覆盖技术&#xff0c;用来解决“程序大小超过物理内存总合”的问题 覆盖技术…

Elasticsearch:Serarch tutorial - 使用 Python 进行搜索 (二)

这个是继上一篇文章 “Elasticsearch&#xff1a;Serarch tutorial - 使用 Python 进行搜索 &#xff08;一&#xff09;” 的续篇。在今天的文章中&#xff0c;我们接着来完成如何进行分页及过滤。 分页 - pagination 应用程序处理大量结果通常是不切实际的。 因此&#xff0…

【QT 5 +Linux下+录屏软件使用+总结说明+使用录屏软件+简单软件使用+SimpleScreenRecorder+操作说明】

【【QT 5 Linux下录屏使用录屏软件简单软件使用SimpleScreenRecorder操作说明】】 1、前言2、实验环境3、录屏软件综述SimpleScreenRecorder&#xff1a;Kazam&#xff1a;OBS Studio (Open Broadcaster Software)&#xff1a;VokoscreenNG&#xff1a;RecordMyDesktop&#xf…

Spring 面试题学习笔记整理

Spring 面试题学习笔记整理 Spring的理解IOC读取 xml注入 配置过程解析注解注入过程 高频 &#xff1a;IOC 理解 及原理 底层实现IoC的底层实现高频&#xff1a;Bean的生命周期&#xff08;图解&#xff09;高频&#xff1a;Bean的生命周期&#xff08;文解&#xff09;扩展知识…

如何使用Cloudreve+Cpolar搭建个人PHP云盘系统并发布公网可访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

【自学笔记】01Java基础-08Java常用API:String、ArrayList集合

记录学习Java基础中有关应用程序接口&#xff08;API&#xff09;的基础知识&#xff0c;包括两个常用类String和ArrayList类的介绍。 什么是API&#xff1f; API是Application Programming Interface&#xff08;应用程序编程接口&#xff09;的缩写&#xff0c;它是一组预先…

陪诊系统|北京陪诊小程序提升陪诊服务效果

随着科技的不断发展&#xff0c;人们对于医疗服务的需求也越来越高。在过去&#xff0c;陪诊师和陪诊公司通常需要通过电话或传真等传统方式与医院进行沟通和安排。然而&#xff0c;现在有了陪诊小程序&#xff0c;这些问题得到了解决。本文将介绍陪诊小程序的开发流程和功能&a…

Python 简单爬虫程序及其工作原理

前言 网络中包含大量的数据&#xff0c;这些数据对于我们来说是非常有价值的&#xff0c;因此编写一个爬虫程序&#xff0c;自动从网页中获取所需的数据&#xff0c;对于信息收集和分析是非常有帮助的。Python 是一种高效而灵活的编程语言&#xff0c;它提供了强大的库和框架来…

SpringBoot学习(三)-整合JDBC、Druid、MyBatis

注&#xff1a;此为笔者学习狂神说SpringBoot的笔记&#xff0c;其中包含个人的笔记和理解&#xff0c;仅做学习笔记之用&#xff0c;更多详细资讯请出门左拐B站&#xff1a;狂神说!!! 一、整合JDBC使用&#xff08;理解&#xff09; 创建项目 勾选依赖启动器 查看依赖 …

vue3 插槽 slot 使用

vue3 插槽 slot 使用 在 Vue3 中&#xff0c;插槽&#xff08;slot&#xff09;是一种重要的组件复用和内容分发机制。通过使用插槽&#xff0c;可以让组件更加灵活和具有可复用性&#xff0c;在不同的地方渲染不同的内容&#xff0c;同时保证相同的样式。 插槽资料 官网介绍&…

【uniapp】多规格选择

效果图 VUE <template> <view><view class"wp-80 pd-tb-40 mg-auto"><button type"warn" click"showDrawer(showRight)">筛选</button></view><!-- 筛选-uni-drawer --><uni-drawer ref"s…

tolist()读取Excel列数据,(Excel列数据去重后,重新保存到新的Excel里)

从Excel列数据去重后&#xff0c;重新保存到新的Excel里 import pandas as pd# 读取Excel文件 file r"D:\\pythonXangmu\\quchong\\quchong.xlsx" # 使用原始字符串以避免转义字符 df pd.read_excel(file, sheet_namenameSheet)# 删除重复值 df2 df.drop_duplica…

HTTP打怪升级之路

新手村 上个世纪80年代末&#xff0c;有一天&#xff0c;Tim Berners-Lee正在工作&#xff0c;他需要与另一台计算机上的同事共享一个文件。他尝试使用电子邮件&#xff0c;但发现电子邮件不能发送二进制文件。Tim Berners-Lee意识到&#xff0c;他需要一种新的协议来共享二进制…

IntelliJ IDEA 如何配置git

在 IntelliJ IDEA 中配置 Git 的步骤如下&#xff1a; 打开 IntelliJ IDEA。找到 File–>Setting–>Version Control–>Git–>Path to Git executable。在 Git 的安装路径下找到 cmd 文件夹下的 git.exe&#xff0c;到此 Git 配置已完成。

Windows 安装配置 Anaconda、CUDA、cuDNN、pytorch-cuda全流程

Windows 安装配置 Anaconda、CUDA、cuDNN、pytorch-cuda全流程 1. 安装Anaconda 网址&#xff1a;https://repo.anaconda.com/archive/ 选择第一个下载即可 双击exe文件&#xff0c;按安装向导安装即可&#xff08;除安装路径自己选择外&#xff0c;其余均可按默认选项&#x…

02 Deep learning algorithm

Neural Networks target&#xff1a; inference&#xff08;prediction&#xff09;training my own modelpractical advice for building machine learning systemdecision Tress application: speech&#xff08;语音识别&#xff09; ----> images(计算机视觉)—> t…

【Redis-09】Redis哨兵机制的实现原理-Sentinel

Sentinel是Redis高可用性的解决方案&#xff1a;由一个或者多个Sentinel实例组成的哨兵系统监视多个主从服务器&#xff0c;并实现主从服务器的故障转移。 Sentinel本质上只是一个运行在特殊模式下的Redis服务器&#xff0c;使用以下命令可以启动并初始化一个Sentinel实例&…