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

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))));
        }

    }
}

 

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【量化金融】《证券投资学》吴晓求(第四版)(更新中)

这里写目录标题 第一篇 基本知识第1章 证券投资工具名词解释简答题 第2章 证券市场名词解释简答题 第二篇 基本分析第三篇 技术分析第四篇 组合管理第五篇 量化分析与交易策略 第一篇 基本知识 第1章 证券投资工具 名词解释 风险&#xff08;risk&#xff09; 未来结果的不…

Hive - Select 使用 in 限制范围

目录 一.引言 二.Select Uid Info 1.少量 Uid 2.大量 Uid ◆ 建表 ◆ 本地 Load ◆ HDFS Load ◆ Select In 三.总结 一.引言 工业场景下 Hive 表通常使用 uid 作为用户维度构建和更新 Hive 表&#xff0c;当我们需要查询指定批次用户信息时&#xff0c;可以使用 in …

Spark内核解析-Spark shuffle6(六)

1、Spark Shuffle过程 1.1MapReduce的Shuffle过程介绍 Shuffle的本义是洗牌、混洗&#xff0c;把一组有一定规则的数据尽量转换成一组无规则的数据&#xff0c;越随机越好。MapReduce中的Shuffle更像是洗牌的逆过程&#xff0c;把一组无规则的数据尽量转换成一组具有一定规则…

极速 JavaScript 打包器:esbuild

文章目录 引言什么是esbuild&#xff1f;esbuild的特点esbuild如何实现如此出色的性能&#xff1f;esbuild缺点基本配置入口文件输出文件模块格式targetplatformexternalbanner和footer 高级配置插件系统自定义插件压缩代码调试代码 结论&#x1f636; 写在结尾 引言 esbuild是…

月薪15000在春晚分会场西安,够花吗?

千寻的结论&#xff1a; 如果有房无贷&#xff0c;另一半也有工作收入&#xff0c;父母身体健康且均有不错的退休金&#xff0c; 满足这些条件的话&#xff0c;在西安月入1.5W是相当不错。

苹果电脑菜单栏应用管理软件Bartender 4 mac软件特点

Bartender mac是一款可以帮助用户更好地管理和组织菜单栏图标的 macOS 软件。它允许用户隐藏和重新排列菜单栏图标&#xff0c;从而减少混乱和杂乱。 Bartender mac软件特点 菜单栏图标隐藏&#xff1a;Bartender 允许用户隐藏菜单栏图标&#xff0c;只在需要时显示。这样可以…

Flutter 图片和资源的高效使用指南

Flutter 应用程序包含代码和 assets&#xff08;也为资源&#xff09;。资源是被打包到应用程序安装包中&#xff0c;可以在运行时访问的一种文件。常见的资源类型包括静态数据&#xff08;例如 JSON 文件&#xff09;&#xff0c;配置文件&#xff0c;图标和图片&#xff08;J…

14:00面试,14:08就出来了,问的问题过于变态了。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到10月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40…

springboot第47集:【思维导图】面向对象,关键字,标识符,变量,数组的使用...

关键字&#xff1a;class,public,static,void等&#xff0c;特点是全部关键字都是小写字母。 image.png image.png 凡是自己起的名字可以叫标识符 image.png image.png image.png image.png 整数类型的使用 image.png image.png image.png 浮点类型 image.png image.png 字符类…

回归预测 | Matlab实现基于GA-Elman遗传算法优化神经网络多输入单输出回归预测

回归预测 | Matlab实现基于GA-Elman遗传算法优化神经网络多输入单输出回归预测 目录 回归预测 | Matlab实现基于GA-Elman遗传算法优化神经网络多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现基于GA-Elman遗传算法优化神经网络多输入单输…

Java重修第一天—学习数组

1. 认识数组 建议1.5倍速学习&#xff0c;并且关闭弹幕。 数组的定义&#xff1a;数组是一个容器&#xff0c;用来存储一批同种类型的数据。 下述图&#xff1a;是生成数字数组和字符串数组。 为什么有了变量还需要定义数组呢&#xff1f;为了解决在某些场景下&#xff0c;变…

安装extiverse/mercury时报错

问题描述 作者在安装 Flarum 的插件 extiverse/mercury 时报错&#xff0c;内容如下图所示 解决方案 ⚠警告&#xff1a;请备份所有数据再进行接下来的操作&#xff0c;此操作可能会导致网站不可用&#xff01; 报错原因&#xff1a;主要问题是在安装过程中解决依赖关系。具…

Day24 回溯算法part01 理论基础 77.组合

回溯算法part01 理论基础 77.组合 理论基础(转载自卡码网) 什么是回溯法 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 在二叉树系列中&#xff0c;我们已经不止一次&#xff0c;提到了回溯&#xff0c;例如二叉树&#xff1a;以为使用了递归&#xff0c;其…

nVisual如何实现数据中心资产管理

背景 随着信息技术的迅速发展&#xff0c;数据中心已经成为了企业信息化建设的重要基础设施之一。数据中心不仅承载着大量的企业数据和业务应用&#xff0c;而且也需要大量的资产投入来支持其运营和发展。 因此&#xff0c;数据中心资产管理的重要性也日益凸显&#xff0c;数…

SparkStreaming基础解析(四)

1、 Spark Streaming概述 1.1 Spark Streaming是什么 Spark Streaming用于流式数据的处理。Spark Streaming支持的数据输入源很多&#xff0c;例如&#xff1a;Kafka、Flume、Twitter、ZeroMQ和简单的TCP套接字等等。数据输入后可以用Spark的高度抽象原语如&#xff1a;map、…

OpenCV-15位运算

OpenCV中的逻辑运算就是对应位置的元素进行与、或、非和异或。 Opencv与Python不同的是&#xff1a;OpenCV中0的非反过来是255&#xff0c;255反过来是0。 但是Python中255非为-256。 一、非运算 使用API---cv.bitwise_not(str) 示例代码如下&#xff1a; import cv2 imp…

Jenkins集成部署java项目

文章目录 Jenkins简介安装 Jenkins简介 Jenkins能实时监控集成中存在的错误&#xff0c;提供详细的日志文件和提醒功能&#xff0c;还能用图表的形式形象的展示项目构建的趋势和稳定性。 官网 安装 在官网下载windows版本的Jenkins 但是我点击这里浏览器没有反应&#xff0…

系列十一、(一)Sentinel简介

一、Sentinel简介 1.1、官网 【英文文档】 https://github.com/alibaba/Sentinel/wiki【中文文档】 https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5 1.2、概述 1.3、功能

JAVA基本语法(关键字,保留字)和快捷键

java基本语法&#xff1a; 1 大小写敏感 2 类名首字母大写 3 变量名、方法名首字母小写&#xff0c;遵循驼峰命名法 4 源文件名必须和类相同 命名法&#xff1a; 驼峰命名法&#xff08;推荐&#xff09;&#xff1a;由若干单词组成&#xff0c;每个单词首字母大写&#…

[足式机器人]Part2 Dr. CAN学习笔记-动态系统建模与分析 Ch02-3流体系统建模

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-动态系统建模与分析 Ch02-12课程介绍电路系统建模、基尔霍夫定律 流量 flow rate q q q m 3 / s m^3/s m3/s 体积 volume V V V m 3 m^3 m3 高度 heigh h h h m m m 压强 pressure p p p …