Java中创建线程的方式以及线程池创建的方式、推荐使用ThreadPoolExecutor以及示例

news2024/11/15 21:35:37

场景

Java中创建线程的方式有三种

1、通过继承Thread类来创建线程

定义一个线程类使其继承Thread类,并重写其中的run方法,run方法内部就是线程要完成的任务,

因此run方法也被称为执行体,使用start方法来启动线程。

2、通过实现Runanle接口来创建线程

首先定义Runnable接口,并重写Runnable接口的run方法,run方法的方法体同样是该线程的线程执行体。

3、通过Callable 和 Future来创建线程

Runnable接口执行的是独立的任务,Runnable接口不会产生任何返回值,

如果希望在任务完成之后能够返回一个值的话,可以实现Callable接口。

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主

实现

Java创建线程的三种方式

1、通过继承Thread类来创建线程

public class TJavaThread extends Thread{
    static int count;

    @Override
    public synchronized void run() {
        for(int i =0;i<10000;i++){
            count++;
        }
    }

    public static void main(String[] args) {
        TJavaThread tJavaThread = new TJavaThread();
        tJavaThread.start();
        try {
            //使用线程的join方法,用来等待线程的执行结束,如果不加join方法,它就不会等待tJavaThread的执行完毕。
            tJavaThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }
}

2、通过实现Runanle接口来创建线程

public class TJavaThreadRunable implements Runnable{

    static int count;

    @Override
    public synchronized void run() {
        for(int i=0;i<10000;i++){
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new TJavaThreadRunable());
        thread.start();
        thread.join();
        System.out.println(count);
    }
}

3、通过Callable 和 Future来创建线程

public class TJavaThreadCallable implements Callable {

    static int count;
    public TJavaThreadCallable(int count){
        this.count = count;
    }

    @Override
    public Object call(){
        return count;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask((Callable<Integer>)()->{
            for (int i =0;i<1000;i++){
                count++;
            }
            return count;
        });
        Thread thread = new Thread(task);
        thread.start();

        Integer total = task.get();
        System.out.println(total);
    }
}

Java使用线程池来创建线程

Executor虽然不是传统线程创建的方式之一,但是它却成为了创建线程的替代者,使用线程池的好处
1、利用线程池能够复用线程、控制最大并发数
2、实现任务线程队列缓存策略和拒绝机制
3、实现某些与时间相关的功能,如定时执行、周期执行等。
4、隔离线程环境。比如两个服务在同一台服务器上,分别开启两个线程池,避免各服务线程相互影响。

ExecutorService是Executor的默认实现,也是Executor的扩展接口,

ThreadPoolExecutor类提供了线程池的扩展实现。Executors类为这些Executor提供了方便的工厂方法。

ExecutorService创建线程的几种方式:

1、CacheedThreadPool

创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。

如果现有线程没有可用的,则创建一个新线程并添加到池中。

终止并从缓存中移除那些已有 60 秒钟未被使用的线程。CacheThreadPool会为每一个任务都创建一个线程

    private static void CacheedThreadPoolTest() {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            //submit()有返回值,而execute()没有
            //submit()可以进行Exception处理
            service.execute(() -> {
                int count = 0;
                for (int j = 0; j < 10000; j++) {
                    count++;
                }
                System.out.println(count);
            });
        }
        service.shutdown();
    }

2、FixedThreadPool

使你可以使用有限的线程集来启动多线程,

可以一次性的预先执行高昂的线程分配,因此也就可以限制线程的数量。

这样可以节省时间,因为你不必为每个任务都固定的付出创建线程的开销。

    private static void FixedThreadPoolTest() {
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            int k = i;
            service.execute(() -> {
                int count = 0;
                for (int j = 0; j < 10000; j++) {
                    count++;
                }
                System.out.println(count);
                System.out.println(Thread.currentThread().getId() + "--" + k);
            });
        }
        //ExecutorService 对象是使用静态的Executors创建的,这个方法可以确定Executor类型。对shutdown的调用可以防止新任务提交给ExecutorService,
        //这个线程在Executor中所有任务完成后退出、
        service.shutdown();
    }

3、SingleThreadExecutor

就是线程数量为1的FixedThreadPool,如果向SingleThreadPool一次性提交了多个任务,

那么这些任务将会排队。每个任务都会在下一个任务开始前结束,所有的任务都将使用相同的线程。

SingleThreadPool会序列化所有提交给他的任务,并会维护它自己的悬挂队列。

从输出结果来看,任务都是挨着进行的。为任务分配五个线程,但是这五个线程不像上面有换进换出的效果,

它每次都会先执行完自己的那个线程,然后余下的线程继续走完这条线程的执行路径。

可以使用SingleThreadExecutor来确保任意时刻都只有唯一一个任务在运行。

    private static void SingleThreadExecutorTest() {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            int k = i;
            service.execute(() -> {
                int count = 0;
                for (int j = 0; j < 10000; j++) {
                    count++;
                }
                System.out.println(count);
                System.out.println(Thread.currentThread().getId() + "--" + k);
            });
        }
        //ExecutorService 对象是使用静态的Executors创建的,这个方法可以确定Executor类型。对shutdown的调用可以防止新任务提交给ExecutorService,
        //这个线程在Executor中所有任务完成后退出、
        service.shutdown();
    }

4、ScheduledThreadPool

常用于需要延迟执行或周期循环执行任务的场景

schedule()方法可以用来延迟任务的执行

运行下面任务,则先输出时间,延迟2秒后才执行

    private static void ScheduledThreadPoolTestSchedule() {
        System.out.println("当前时间:" + System.currentTimeMillis());
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        service.schedule(
                () ->
                System.out.println("开始执行:" + System.currentTimeMillis()), 2, TimeUnit.SECONDS);
        service.shutdown();
    }

scheduleAtFixedRate()方法 固定频率执行方法

    private static void ScheduledThreadPoolTestFixedRate(){
        System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },2,1,TimeUnit.SECONDS);
    }

scheduleWithFixedDelay  固定的间隔时间执行任务

    private static void ScheduledThreadPoolTestFixedDelay(){
        System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
        executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },2,1,TimeUnit.SECONDS);
    }

scheduleAtFixedRate与scheduleWithFixedDelay区别?

scheduleAtFixedRate的下一次执行时间是上一次执行时间+间隔时间

scheduleWithFixedDelay下一次执行时间是上一次执行时间结束时系统时间+间隔时间

scheduleAtFixedRate执行结果

scheduleWithFixedDelay执行结果

 

5、newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;

    private static void SingleThreadScheduleTest(){
        System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
            }
        },2,1,TimeUnit.SECONDS);
    }

6、newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)

又称任务窃取线程池,可以传入线程的数量,不传入,则默认使用当前计算机中可用的cpu数量,

实际的线程数可能会动态增长和收缩,不能保证提交任务的执行顺序。

以下为设置线程数为4

    private static void WorkStealingPoolTest(){
        ExecutorService executorService = Executors.newWorkStealingPool(4);
        for (int i =0;i<10;i++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    String name = Thread.currentThread().getName();
                    System.out.println(name+"开始执行");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name+"执行结束");
                }
            });
        }
        System.out.println("cpu核心数:"+ Runtime.getRuntime().availableProcessors());
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

运行效果

 

这里的cpu核心数为8,如果不设置线程数则直接

ExecutorService executorService = Executors.newWorkStealingPool();

此时执行结果

 

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式

《阿里巴巴JAVA开发手册》有这样一条强制规定:

线程池不允许使用Executors去创建,而应该通过ThreadPoolExecutor方式,这样处理方式更加明确线程池运行规则,

规避资源耗尽风险。

说明: Executors 返回的线程池对象的弊端如下:
 (1) FixedThreadPool 和 SingleThreadPool :
    允许的请求队列的长度可能会堆积大量的请求,从而导致 OOM。
 (2) CachedThreadPool :
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

ThreadPoolExecutor参数说明

        corePoolSize - 线程池核心线程数量
        maximumPoolSize - 线程池最大数量
        keepAliveTime - 空闲线程存活时间
        unit - 时间单位
        workQuene - 线程池中所使用的缓冲队列
        handler - 线程池对拒绝任务的处理策略

ThreadPoolExecutor执行流程

 

示例代码

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.AbortPolicy());
        for(int i =1;i<=7;i++){
            String task = "task:"+i;
            threadPoolExecutor.execute(new ThreadPoolTask(task));
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

任务具体实现类

    static class ThreadPoolTask implements Runnable{

        private String taskName;

        ThreadPoolTask(String task){
            this.taskName = task;
        }

        @Override
        public void run() {
            System.out.println("启动:"+taskName);
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

这里假设每个任务需要执行10秒,每隔0.5秒执行一个任务。

当循环数为7,即不大于最大线程数+队列大小时,执行结果如下
    启动:task:1
    启动:task:2
    启动:task:6
    启动:task:7
    启动:task:3
    启动:task:4
    启动:task:5

 

执行结果分析:

提交1、2两个任务,判断小于corePoolSize,会为每一个任务创建一个线程

提交3、4、5三个任务时,判断正在执行的任务数量为2,且每个任务执行时间为10s,所以会将这三个放入到workQueue中等待执行

提交6、7两个任务时,因为workQuene队列的大小为3,此时workQueue队列中存储的任务数量满了,

会判断当前线程池中正在执行的任务是否小于maximumPoolSize,这里是4,

如果小于4则创建新的线程来执行任务6和7,此时7个任务都提交完毕,那么等待任务3、4、5会在前面每个10s的任务执行完之后执行。

当修改循环数为10

    启动:task:1
    启动:task:2
    启动:task:6
    启动:task:7
    Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.ruoyi.demo.thread.threadpool.ThreadPool$ThreadPoolTask@deb6432 rejected from java.util.concurrent.ThreadPoolExecutor@28ba21f3[Running, pool size = 4, active threads = 4, queued tasks = 3, completed tasks = 0]
     at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
     at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
     at com.ruoyi.demo.thread.threadpool.ThreadPool.ThreadPoolExecutorTest(ThreadPool.java:230)
     at com.ruoyi.demo.thread.threadpool.ThreadPool.main(ThreadPool.java:280)
    启动:task:3
    启动:task:4
    启动:task:5

   

 

执行结果分析,当执行第8个任务时,判断执行线程总数大于最大线程数+队列大小,直接执行拒绝策略,同理任务9和10也是如此。
    这里的拒绝策略是AbortPolicy

拒绝策略

1、AbortPolicy - 丢弃任务并抛出RejectedExecutionException异常

2、CallerRunsPolicy - 将被拒绝的任务添加到线程池正在运行的线程中去执行

将上面修改之后的执行结果

    //启动:task:1
    //启动:task:2
    //启动:task:6
    //启动:task:7
    //启动:task:8
    //启动:task:3
    //启动:task:4
    //启动:task:5
    //启动:task:9
    //启动:task:10

3、DiscardPolicy - 丢弃任务,但是不抛出异常
    //此时执行结果如下
    //启动:task:1
    //启动:task:2
    //启动:task:6
    //启动:task:7
    //启动:task:3
    //启动:task:4
    //启动:task:5

4、DiscardOldestPolicy - 丢弃队列最前面的任务,然后重新尝试执行任务
    //此时执行结果如下
    //启动:task:1
    //启动:task:2
    //启动:task:6
    //启动:task:7
    //启动:task:8
    //启动:task:9
    //启动:task:10

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

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

相关文章

盘点手机Type-c充电接口5个实用功能

目录 1、手机通过Type-c转HDMI&#xff0c;高清输出电视 2、通过OTG Type-c扩展手机功能 3、实现手机通过网卡有线上网 4、电脑通过手机Type-C有线上网 5、手机通过Type-C收听高清音频 今天给大家聊聊手机Type-c充电接口的5个实用功能&#xff0c;希望对大家日常使用…

Java每日一练(20230410)

目录 1. 二叉树的锯齿形层序遍历 &#x1f31f;&#x1f31f; 2. 从中序与后序遍历序列构造二叉树 &#x1f31f;&#x1f31f; 3. 平衡二叉树 &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专…

UE4 Niagara 烟花制作工程

效果图&#xff1a; 第一个做的是网上射出的粒子&#xff1a; 用了事件&#xff0c;把ID给启用&#xff0c;不然会报错 第一个发射的炮弹粒子制作完成 第二制作炮弹的拖尾&#xff1a; 很明显会用到Ribbon 让宽度从宽到窄 让位置和颜色与第一个做的粒子一致&#xff0c;所以…

【分享】集简云审批支付助手,实现OA付款单自动到招商银行支付

场景描述 支付管理是企业财务管理中的重要一环&#xff0c;直接涉及企业现金流的管理和资金的运用。 在现代商业环境下&#xff0c;企业支付管理越来越复杂&#xff0c;许多公司都存在支付流程不规范、支付环节复杂的问题&#xff0c;导致企业支付效率低下。一方面&#xff0…

CocosCreator实战篇 |CocosCreator实现《飞机大战》

&#x1f4e2;博客主页&#xff1a;肩匣与橘 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由肩匣与橘编写&#xff0c;首发于CSDN&#x1f649; &#x1f4e2;生活依旧是美好而又温柔的&#xff0c;你也…

论文赏析——约翰·科斯塔斯:线性系统编码

© 1952 J. P. Costas © 2023 Conmajia 作者简介 约翰彼得科斯塔斯&#xff08;1923-2008&#xff09;&#xff0c;美国电气工程师&#xff0c;曾发明科斯塔斯环和科斯塔斯数组。科斯塔斯参加过第二次世界大战&#xff0c;并在战后进入麻省理工学院攻读博士学位&#…

Java反序列化漏洞及实例详解

目录 一、序列化和反序列化 序列化 用途 二、Java反序列化漏洞 数据出现 函数接口 漏洞发现 漏洞利用 三、Java序列化反序列化演示 四、靶场演示 一、序列化和反序列化 序列化 把 Java 对象转换为字节序列&#xff08;字节流&#xff09;的过程。 反序列化 把字节序…

WebRTC 系列(一、简介)

一、什么是 WebRTC WebRTC 全称是 Web RealTime Communication&#xff0c;是一个用于实时通讯的技术&#xff0c;Google 公司在 2010 年用 6829 万美元将其从 Global IP Solutions 公司收购&#xff0c;并于 2011 年开源&#xff0c;不得不说 Google 确实是一家伟大的公司&am…

uniapp 引入插件市场的echart插件,使用指南!+踩坑记录!!血泪史!!!

此文章适用于微信小程序中&#xff0c;在uniapp做的小程序中使用echarts 插件地址&#xff1a;echarts-for-wx - DCloud 插件市场 插件网页往下翻有详细的使用步骤&#xff0c;这里简短的贴上几张图片 以下是我 项目中的使用情况&#xff0c;分享给大家&#xff1a; 1.一键导…

网关和spring cloud网关技术Gateway

文章目录一、网关1.1 网关是什么1.2 API网关的作用二、Spring Cloud Gateway2.1 简介2.2 搭建步骤2.3 路由断言工厂2.3.1 什么是路由断言工厂2.3.2 Spring Cloud Gateway提供的11 种断言工厂2.4 网关过滤器2.4.1 介绍2.4.2 网关过滤器作用2.4.3 过滤器配置2.4.4 全局过滤器Glob…

【STM32学习】模数转换器——ADC

【STM32学习】模数转换器——ADC零、参考一、ADC转换耗时二、转换模式三、对某些寄存器的理解1、ADC_CR22、ADC_SQRX四、库函数注意事项零、参考 STM32固件库&#xff08;标准外设库&#xff09;入门学习 第七章 ADC数模转换&#xff08;一&#xff09; 刘凯&#xff1a;STM32…

flyway:数据库移植框架,再也不用担心历史版本表结构管理问题

0. 引言 在开发周期较长&#xff0c;或者需求变更频繁&#xff0c;涉及多版本发布的项目中&#xff0c;我们常常遇到这样的问题&#xff1a; 改了表结构&#xff0c;开发环境执行了&#xff0c;忘记保存&#xff0c;发测试、生产环境时又要重写一遍DDL多人开发&#xff0c;都…

ChatGPT如何应用在数据治理的具体场景中?

自从ChatGPT爆火以来&#xff0c;技术圈的爆炸新闻是一波接一波&#xff0c;让人目不暇接&#xff0c;突然有了一种“人间一日&#xff0c;AI一年的感觉”。上周就有几条重磅新闻&#xff0c;来跟大家分享一下&#xff1a; New Bing 对所有用户放开&#xff0c;注册即可用周三…

每个企业经营者都应该了解的几个网络安全趋势

每个企业主都应了解的一些网络安全趋势&#xff1a; 1. 对实时数据可见性的需求增加 根据 IBM 发布的调查数据&#xff0c;企业发现并遏制漏洞的平均时间为 277 天。这种漏洞得不到解决的时间越长&#xff0c;泄露的数据就越多。这反过来会对您的业务产生更大的影响。企业需要…

【无人机】基于灰狼优化算法的无人机路径规划问题研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

蓝桥杯0基础学习

目录 Split()分隔字符串 StringBuilder用法 反转字符串中的单词 LocalDate用法 LocalDate类具体方法 查询年月日 修改时间 判断日期先后&#xff0c;是否是闰年&#xff0c;月份年份长度 对时间进行加减操作 跑步锻炼 使用LoaclDate解法 没使用LocalDate解法 next()…

Linux学习_设备树理论

Linux学习_设备树总结三种写驱动的方法资源和驱动在同一个文件里资源用 platform_device 指定、驱动在 platform_driver 实现资源用设备树指定驱动在 platform_driver 实现设备树的语法实际使用时&#xff0c;include模板小改常用属性#address-cells、#size-cells、regcompatib…

DBSCAN聚类算法及Python实现

DBSCAN聚类算法 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的聚类算法&#xff0c;可以将数据点分成不同的簇&#xff0c;并且能够识别噪声点&#xff08;不属于任何簇的点&#xff09;。 DBSCAN聚类算法的基…

告诉老默我想学Spring Cloud了(新手篇):从0到1搭建Spring Cloud项目(实际项目开发的浓缩精华版)

告诉老默我想学Spring Cloud了&#xff08;新手篇&#xff09;&#xff1a;从0到1搭建Spring Cloud项目一、前言二、如何选择版本2.1 SpringCloud 和 Spring Boot 版本选型2.1.1 Spring Cloud 版本与 Spring Boot 版本关系2.1.2 选择具体的合适版本2.2 第三方组件的版本选型三、…

SpringCloud之Eureka原理分析与实战(注册与发现)

目录 1、从本质理解服务治理思想 2、为什么选择Spring Cloud服务治理组件 3、Spring Cloud Eureka服务发现 3.1 Eureka的优势 3.2 Eureka架构组成 3.3 搭建Eureka Server 实战 3.3.1 添加依赖 3.3.2 开启服务注册 3.3.3 添加YML配置 3.3.4 访问服务 3.4 搭建Eureka …