创建多线程的四种方式

news2024/12/23 13:35:24

目录儿

  • 一、创建线程的四种方式
    • 1. 继承Thread类
    • 2. 实现Runnable接口
    • 3. 实现Callable接口
    • 4. 线程池
      • 禁止使用 Executors 构建线程池
      • 构建线程池的正确方式

一、创建线程的四种方式

1. 继承Thread类

① 创建一个类继承Thread类,重写run()方法
② 调用start()方法启动线程
例:

/* 创建三个窗口卖票,总票数为100 */
public class TicketWindow  extends Thread {

    //线程共享资源(100张票)
    private static int ticket = 100;
    //同步锁必须是唯一的
    private static final Object obj = new Object();

    @Override
    public void run() {
        while(true){
            synchronized(obj){
                if(ticket > 0){

                    //暂停当前线程,允许其它具有相同优先级的线程获得运行机会
                    Thread.yield();

                    System.out.println(getName() + "卖票: 票号:" + ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        TicketWindow w1 = new TicketWindow();
        w1.setName("一号窗口");
        w1.start();

        TicketWindow w2 = new TicketWindow();
        w2.setName("二号窗口");
        w2.start();

        Thread w3 = new TicketWindow(); //TicketWindow继承了Thread,可以用Thread接收TicketWindow对象
        w3.setName("三号窗口");
        w3.start();
    }
}

2. 实现Runnable接口

① 创建类实现Runnable接口,重写run()方法
② 以实现类作为构造器参数,创建一个线程(Thread)对象
③ 调用start()方法启动线程

/* 创建三个窗口卖票,总票数为100 */
public class TicketWindow  implements Runnable {

    //线程共享资源(100张票)
    private static int ticket = 100;
    //同步锁必须是唯一的
    private static final Object obj = new Object();

    @Override
    public void run() {
        while(true){
            synchronized(obj){
                if(ticket > 0){
                    //暂停当前线程,允许其它具有相同优先级的线程执行(不能保证运行顺序)
                    Thread.yield();
                    System.out.println(Thread.currentThread().getName() + "卖票: 票号:" + ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        TicketWindow ticketWindow = new TicketWindow();

        new Thread(ticketWindow, "一号窗口").start();
        new Thread(ticketWindow, "二号窗口").start();
        new Thread(ticketWindow, "三号窗口").start();
    }
}

注意:实现Runnable接口方式中,调用的不是Thread类的run()方法,而是在线程启动后,去调用Runnable类型的run()方法,也就是我们传入的实现类中的run()方法

3. 实现Callable接口

① 创建类实现Callable接口,重写call()方法
② 创建实现类对象
③ 将实现类对象作为构造器参数,创建FutureTask对象
FutureTask对象作为构造器参数,创建Thread对象
⑤ 调用Thread对象的start()方法启动线程
⑥ 调用FutureTask对象的get()方法获取返回值
例:

// 1. 创建一个类来实现Callable接口
public class TicketWindow  implements Callable<Object> {

    //线程共享资源(100张票)
    private static int ticket = 100;
    //同步锁必须是唯一的
    private static final Object obj = new Object();

    // 2. 重写call()方法,返回值类型可以根据需求指定,但必须与创建FutureTask对象时里面的泛型一致
    @Override
    public Object call() {
        while(true){
            synchronized(obj){
                if(ticket > 0){

                    //暂停当前线程,允许其它具有相同优先级的线程获得运行机会
                    Thread.yield();

                    System.out.println(Thread.currentThread().getName() + "卖票: 票号:" + ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
        return Thread.currentThread().getName() + "执行完毕 ~ ~";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 3. 创建Callable接口实现类的对象
        TicketWindow ticketWindow = new TicketWindow();
        
        // 4. 将实现类对象作为参数传到FutureTask类的构造器中,创建FutureTask类的对象
        FutureTask<Object> futureTask1 = new FutureTask<Object>(ticketWindow);
        // 5. 将FutureTask类对象传到Thread类的构造器中,创建线程对象(因为FutureTask实现了Runnable接口,所以可以这样传)
        Thread t1 = new Thread(futureTask1, "1号窗口");
        // 6. 通过线程对象调用start()方法开启线程
        t1.start();


        FutureTask<Object> futureTask2 = new FutureTask<Object>(ticketWindow);
        Thread t2 = new Thread(futureTask2, "2号窗口");
        t2.start();

		FutureTask<Object> futureTask3 = new FutureTask<Object>(ticketWindow);
        Thread t3 = new Thread(futureTask3, "3号窗口");
        t3.start();

        // 8. 调用FutureTask类的get()方法获取call()方法的返回值,如果不需要返回值可以省略这一步
        Object result1= futureTask1.get();
        Object result2= futureTask2.get();
        Object result3= futureTask3.get();
        System.out.println(result);
    }
}

4. 线程池

使用线程池创建线程,是实际项目开发中最常用的方式,它拥有许多好处:

  • 提高响应速度 (因为减少了创建新线程的时间)
  • 降低资源损耗 (重复利用线程池中的线程,不需要每次都创建和销毁)
  • 便于线程的管理
import java.util.concurrent.*;

/* 水果售卖窗口 */
class FruitWindow implements Runnable {

    private int fruitNumber = 100;

    @Override
    public void run() {
        while (true) {
            /* 1.任何一个类的对象,都可以充当锁,一般使用Object类对象 */
            /* 2.多个线程必须共用一把锁,多个线程抢一把锁,谁抢到谁执行 */
            synchronized (this) {
                if (fruitNumber > 0) {
                    try {
                        //休眠30毫秒
                        TimeUnit.MILLISECONDS.sleep(30);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "卖第:" + fruitNumber + "份水果");
                    fruitNumber--;

                } else {
                    break;
                }
            }
        }
    }
}

/* 蔬菜售卖窗口 */
class VegetableWindow implements Callable<Object> {

    private int vegetableNumber = 100;

    @Override
    public Object call() {
        while (true) {
            /* 1.任何一个类的对象,都可以充当锁,一般使用Object类对象 */
            /* 2.多个线程必须共用一把锁,多个线程抢一把锁,谁抢到谁执行 */
            synchronized (this) {
                if (vegetableNumber > 0) {
                    try {
                        //休眠20毫秒
                        TimeUnit.MILLISECONDS.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "卖第:" + vegetableNumber + "份蔬菜");
                    vegetableNumber--;

                } else {
                    break;
                }
            }
        }
        return null;
    }
}


public class ThreadPool {

    public static void main(String[] args) {
        // 1.提供指定线程数量的线程池
        ExecutorService pool= Executors.newFixedThreadPool(10);

        // 设置线程池的属性 (线程管理)
        ThreadPoolExecutor poolconfig = (ThreadPoolExecutor) pool; //设置属性前,需要将接口类型 强转为 实现类类型
        poolconfig .setCorePoolSize(10); //核心池的大小(最小线程数)
        poolconfig .setMaximumPoolSize(15); //最大线程数
        //参数1:时间值。时间值为零将导致多余的线程在执行任务后立即终止
        //参数2:时间单位,使用TimeUnit类指定,TimeUnit.DAYS为天、TimeUnit.HOURS为小时、TimeUnit.MINUTES为分钟、TimeUnit.SECONDS为秒,TimeUnit.MICROSECONDS为微秒
        poolconfig .setKeepAliveTime(5, TimeUnit.MINUTES); //空闲线程存活时间
        // .  .  .

        // 2.执行指定的线程操作 (需要提供实现了Runnable接口 或 Callable接口实现类的对象  )
        FruitWindow fruitWindow = new FruitWindow();
        /* 注意:操作共享数据的线程必须共用同一把锁 */
        pool.execute(fruitWindow); //适用于Runnable
        pool.execute(fruitWindow); //适用于Runnable
        pool.execute(fruitWindow); //适用于Runnable

        VegetableWindow vegetableWindow = new VegetableWindow();
        /* 注意:操作共享数据的线程必须共用同一把锁 */
        pool.submit(vegetableWindow); //适用于Callable
        pool.submit(vegetableWindow); //适用于Callable
        Future<Object> future = pool.submit(vegetableWindow); //适用于Callable

        // 获取call()方法的返回值
        Object result = future.get();
        System.out.println(result);

        pool.shutdown(); //关闭连接池
    }
}

禁止使用 Executors 构建线程池

上面的例子用到了Executors静态工厂构建线程池,但一般不建议这样使用
Executors是一个Java中的工具类。提供工厂方法来创建不同类型的线程池。
在这里插入图片描述
虽然它很大程度的简化的创建线程池的过程,但是它有一个致命缺陷:Java开发手册中提到,使用Executors创建线程池可能会导致OOM(Out-OfMemory , 内存溢出 )

原因:
Executors的静态方法创建线程池时,用的是 LinkedBlockingQueue阻塞队列,如创建固定线程池的方法newFixedThreadPool()

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

Java 中的 BlockingQueue 主要有两种实现,分别是 :

  • ArrayBlockingQueue
  • LinkedBlockingQueue

ArrayBlockingQueue 是一个用数组实现的有界阻塞队列,必须设置容量。
LinkedBlockingQueue 是一个用链表实现的有界阻塞队列,在不设置的情况下,将是一个无边界的阻塞队列,最大长度为 Integer.MAX_VALUE

这里的问题就出在这里,newFixedThreadPool 中创建 LinkedBlockingQueue 时,并未指定容量。此时,LinkedBlockingQueue 就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。

上面提到的问题主要体现在 newFixedThreadPoolnewSingleThreadExecutor 两个工厂方法上
newCachedThreadPoolnewScheduledThreadPool 这两个方法虽然没有用LinkedBlockingQueue阻塞队列,但是它默认的最大线程数是Integer.MAX_VALUE,也会有导致OOM的风险。

// 缓存线程池
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
// 周期线程池
public ScheduledThreadPoolExecutor(int corePoolSize) {
   super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}

构建线程池的正确方式

避免使用 Executors 创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时,给 BlockQueue 指定容量就可以了:

private static ExecutorService executor = new ThreadPoolExecutor(
												10,
												10,
												60L,
												TimeUnit.SECONDS,
												new ArrayBlockingQueue(10)
												);

还要指定拒绝策略,处理好任务队列溢出时的异常问题。

参考资料:

  1. CSDN CD4356 Java 多线程详解(二):创建线程的4种方式
    https://blog.csdn.net/weixin_42950079/article/details/124862582
  2. Java开发手册
    http://static.kancloud.cn/mtdev/java-manual/content/%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A6%81%E6%AD%A2%E4%BD%BF%E7%94%A8Executors%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B%E6%B1%A0%EF%BC%9F.md

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

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

相关文章

AI卷入618战场;印象AI开放次数限制;2023 AIGC人才趋势洞察报告;员工瞒着老板悄悄用AI;超好用的AI头像生成教程 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 澳洲游戏媒体 Gamurs 招聘AI编辑&#xff0c;被各路媒体口诛笔伐 上周&#xff0c;澳洲知名游戏媒体集团 Gamurs 在官网招聘「AI Edit…

负载测试和压力测试有何区别?资深测试老鸟总结,一篇搞定...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 负载与压力测试 …

js中数组对象去重的几种方式

js中数组对象去重的几种方式 1、方法一&#xff1a;双层for循环2、对象访问属性的方法3、Map()方法4、reduce方法 首先我们定义数组的形式 let arrObj [{ name: "张三", key: 1 },{ name: "李四", key: 2 },{ name: "王五", key: 3 },{ name: &…

基于langchain+chatGLM搭建部署本地私有化知识库系统

前言 一、 自主GPT 所谓自主&#xff08;autonomous&#xff09;GPT是设计一个Agent&#xff0c;让它自己做计划、决策和动作&#xff0c;通过源源不断的迭代&#xff0c;去完成设定的目标。比如 AutoGPT 。 AutoGPT把GPT的能力推向了更高的应用层次。设定一个任务&#xff…

mac下qtcreator代码格式化

代码开发&#xff1a;qtcreator qtcreator 插件&#xff1a;Beautifier 格式化工具&#xff1a;clang-format 1、Beautifier插件安装 将复现框勾选后重启qtcreator即可。 2、安装clang-format工具 &#xff08;1&#xff09;打开终端输入下面命令等待安装完成 brew install…

Python基础(19)——Python函数讲解一

Python基础&#xff08;19&#xff09;——Python函数讲解一 文章目录 Python基础&#xff08;19&#xff09;——Python函数讲解一目标一. 函数的作用二. 函数的使用步骤2.1 定义函数2.2 调用函数2.3 快速体验 三.函数的参数作用四.函数的返回值作用4.1 应用 五.函数的说明文档…

大快人心,华为EDA领域的新突破,关联软件已取得全面适配

EDA被称为“芯片之母”&#xff0c;是集成电路、电子信息&#xff0c;甚至是全球数字经济的赋能者&#xff0c;是许多电子产业链的基石。 一直以来&#xff0c;就被国际的三大巨头占领&#xff1a;美国Synopsys、美国Cadence、德国Mentor Graphics&#xff0c;市场份额高达90%。…

locust学习教程(6)- 使用更快的http客户端:FastHttpUser

目录 1、概念 2、估算电脑允许的最大并发数 3、fasthttpuser的使用 &#x1f381;更多干货 完整版文档下载方式&#xff1a; 1、概念 Locust 的默认 HTTP 客户端使用的是 python-requests 库。如果我们需要运行非常高的吞吐量测试&#xff0c;去判断吞吐量是否达到预期值&…

免费文字转语音软件哪个好?推荐这三款文字转语音软件给你

文字转语音软件可以将我们输入的文字内容转化为人声朗读出来&#xff0c;这在很多场合都非常实用。比如&#xff0c;在开车或者做家务时&#xff0c;无法手持手机进行阅读&#xff0c;但是通过文字转语音功能&#xff0c;就可以轻松地听取所需内容。然而&#xff0c;市面上的文…

贴吧私信辅助软件工具开发

贴吧私信辅助软件工具开发&#xff0c;贴吧无线私信&#xff0c;用好贴吧也是一个大流量途径 【引流必备】最新外面卖1000多一套的贴吧私信机&#xff0c;日发私信十万条【详细视频操作教程软件】 服务时间&#xff1a;&#xff08;8&#xff1a;00—23&#xff1a;00&#xf…

MySQL数据库——事务

MySQL数据库——事务 一、事务的概念二、事务的ACID特点1.原子性2.一致性3.隔离性4.持久性5.事务隔离级别的作用范围 三、事务级别的查看与设置1.查询全局事务隔离级别2.查询会话事务隔离级别3.设置全局事务隔离级别4.设置会话事务隔离级别 四、事务控制语句1.测试提交事务2.测…

ssm 疫情防控数据可视化平台-计算机毕设 附源码87063

ssm 疫情防控数据可视化平台 摘要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对疫情防控数据可…

图像直方图笔记

图像直方图 在统计学中&#xff0c;直方图是一种对数据分布情况的图形化表示方法。 图像直方图是用来表示数字图像中亮度分布的直方图&#xff0c;使用图像直方图可以很直观地观察到该图的亮度分布情况。在图像直方图中&#xff0c;横轴从左到右分别表示了从纯黑到纯白区域的亮…

【C语言初阶】函数的具体用法,有这篇博客就够了

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello&#xff0c;这里是君兮_&#xff0c;今天又又又来给大家更新0基础C语言中的内容啦&#xff01;今天给大家带来的是C语言当中函数的调用以及使用&#xff0c;废话不多说我们直接开始吧&#xff01; 函数详解 一.函数…

专治疑难系列 - 解决打印机凭证冲突问题

‍‍&#x1f3e1;博客主页&#xff1a; Passerby_Wang的博客_CSDN博客-系统运维,云计算,Linux基础领域博主 &#x1f310;所属专栏&#xff1a;『专治疑难系列』 &#x1f30c;上期文章&#xff1a; 专治疑难系列 - 解决Ubuntu忘记root密码问题 &#x1f4f0;如觉得博主文章…

深度学习实例分割篇——Mask RCNN原理详解篇

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;专栏推荐&#xff1a;深度学习网络原理与实战 &#x1f34a;近期目标&#xff1a;写好专栏的每一篇文章 &#x1f34a;支持小苏&#xff1a;点赞&#x1f44d;&#x1f3fc;、…

如何系列 如何使用SikuliX执行自动化任务

文章目录 什么是SikuliX&#xff1f;SikuliX的使用场景安装SikuliX常用方法查找鼠标键盘其他 示例脚本示例一 自动抢票示例二 自动打开计算器示例三 自动访问CSDN博客搜索博主并关注其他 SikuliX原理SikuliX脚本Sikuli 源文件夹或压缩文件&#xff08;.sikuli、.skl&#xff09…

电源纹波测试,居然还能这么玩

开关稳压器因其具有非常高的效率优势&#xff0c;正在各个领域逐渐替代线性稳压器。 但由于开关稳压器通常被认为具有很大的输出纹波(Ripple)&#xff0c;所以很多工程师在高性能和噪声敏感型系统中只考虑使用低压差(LDO)稳压器。 而事实上&#xff0c;现今很多高性能开关稳压…

人机接口回路原理(三)

四、串行通信接口电路 &#xff08;一&#xff09;人机接口与保护CPU之间的串行通信 1&#xff0e;串行通信接口电路及其作用 人机接口与保护CPU之间的串行通信的作用是人机对话和巡检&#xff0c;其电路见图1&#xff0d;33所示。这个串行通信系统是主从分布式的系统&#x…

双向可控硅控制后续篇:过零检测电路、丢波、斩波、定时

概念讲解 当我们在使用AC负载的时候&#xff0c;为了能较好的控制负载工作功率&#xff0c;需要用到继电器、可控硅等对负载进行工作与断开的控制&#xff0c;从而将功率维持在所需的大小上&#xff0c;之前介绍双向可控硅的文章也讲了其控制方式主要有&#xff1a;定时、丢波…