菜鸟之路Day20一一多线程(二)

简介: ### 菜鸟之路Day20——多线程(二)作者:blue 日期:2025.2.27本文通过多个练习详细介绍了Java多线程的应用,包括买票、送礼、抢红包等场景的模拟。重点讲解了`Thread`类和`Runnable`接口的使用,以及如何利用锁机制确保线程安全。此外,还探讨了线程池的概念及其优势,并提供了创建和配置线程池的具体示例。最后,讨论了如何根据CPU并行数合理设置线程池大小,以优化性能。通过这些练习,读者可以深入理解多线程编程的核心概念和实际应用技巧。

菜鸟之路Day20一一多线程(二)

作者:blue

时间:2025.2.27

0.概述

内容学习自黑马程序员BV1yW4y1Y7Ms

1.多线程练习1

一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,

要求:用多线程模拟买票的过程并打印剩余电影票的数量

Thread中的Run方法:

public class MyThread extends Thread{
   
    //电影票的总数
    static int tickets = 1000;
    //锁对象
    static final Object lock = new Object();

    @Override
    public void run() {
   
        while(true){
   
            synchronized (lock){
   
                if(tickets==0){
   
                    break;
                }
                else{
   
                    try {
   
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
   
                        throw new RuntimeException(e);
                    }
                    tickets--;
                    System.out.println(Thread.currentThread().getName()+"分发了一张电影票剩余"+tickets+"张");
                }
            }
        }
    }
}

测试类:

public class MyThreadTestDemo1 {
   
    public static void main(String[] args) {
   
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t1.start();
        t2.start();
    }
}

2.多线程练习2

有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出。

利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来,

Thread中的Run方法

public class MyThreadTwo extends Thread{
   
    //礼物
    static int gift = 100;
    //锁对象
    static final Object lock = new Object();
    @Override
    public void run() {
   
        while(true){
   
            synchronized (lock){
   
                if(gift<10) {
   
                    break;
                }
                else {
   
                    gift--;
                    System.out.println(Thread.currentThread().getName()+"送出一份礼物,还剩下"+gift+"份礼物");
                }
            }
        }
    }
}

测试类:

public class MyThreadTestDemo2 {
   
    public static void main(String[] args) {
   
        MyThreadTwo t1 = new MyThreadTwo();
        MyThreadTwo t2 = new MyThreadTwo();
        t1.setName("小L");
        t2.setName("小N");
        t1.start();
        t2.start();
    }
}

3.多线程练习3

同时开启两个线程,共同获取1-100之间的所有数字

要求:将输出所有的奇数。

Thread中的Run方法

public class MyThreadThree extends Thread{
   
    static int num = 1;
    static final Object lock = new Object();

    @Override
    public void run() {
   
        while(true){
   
            synchronized (lock) {
   
                if(num>100) {
   
                    break;
                }
                else {
   
                    if(num%2==1){
   
                        System.out.println(num);
                    }
                    num++;
                }
            }
        }
    }
}

测试类

public class MyThreadTestDemo3 {
   
    public static void main(String[] args) {
   
        MyThreadThree t1 = new MyThreadThree();
        MyThreadThree t2 = new MyThreadThree();
        t1.start();
        t2.start();
    }
}

4.多线程练习4

抢红包也用到了多线程。

假设:100块,分成了3个包,现在有5个人去抢。

其中,红包是共享数据。

5个人是5条线程。

打印结果如下:

​ XXX抢到了XXX元
​ XXX抢到了XXX元
​ XXX抢到了XXX元
​ XXX没抢到
​ XXX没抢到

public class MyThreadFour extends Thread {
   
    //共享数据
    //红包总金额
    static double money = 100;
    //红包总数
    static int count = 3;
    //最小金额
    static final double MIN = 0.01;
    //锁对象
    static final Object LOCK = new Object();

    @Override
    public void run() {
   
        synchronized (LOCK) {
   
            if (count == 0) {
   
                System.out.println(Thread.currentThread().getName() + "没抢到");
            } else {
   
                if (count == 1) {
   
                    //最后一个人,红包金额不用随机
                    System.out.println(Thread.currentThread().getName() + "抢到了" + money + "元");
                } else {
   
                    //红包金额需要随机
                    //随机的范围
                    //以第一人举例:最极端的范围是:99.8   0.01  0.01
                    //99.8 = 100-(count-1)*MIN
                    double bounds = money - (count - 1) * MIN;
                    Random rd = new Random();
                    //随机到的金额
                    double value = rd.nextDouble(bounds) + MIN;
                    System.out.println(Thread.currentThread().getName() + "抢到了" + value + "元");
                    money = money - value;
                }
                count--;
            }
        }
    }
}
public class MyThreadTestDemo4 {
   
    public static void main(String[] args) {
   
        MyThreadFour t1 = new MyThreadFour();
        MyThreadFour t2 = new MyThreadFour();
        MyThreadFour t3 = new MyThreadFour();
        MyThreadFour t4 = new MyThreadFour();
        MyThreadFour t5 = new MyThreadFour();

        t1.setName("a");
        t2.setName("b");
        t3.setName("c");
        t4.setName("d");
        t5.setName("e");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();

    }
}

5.多线程练习5

有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为

{10,5,20,50,100,200,500,800,2,80,300,700}

创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”

随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:

每次抽出一个奖项就打印一个(随机)

抽奖箱1 又产生了一个 10 元大奖

抽奖箱1 又产生了一个 100 元大奖

抽奖箱1 又产生了一个 200 元大奖

抽奖箱1 又产生了一个 800 元大奖

抽奖箱2 又产生了一个 700 元大奖

Thread中的Run方法

public class MyThreadFive extends Thread{
   
    //共享数据
    ArrayList<Integer> list;

    static final Object LOCK = new Object();

    public MyThreadFive(ArrayList<Integer> list) {
   
        this.list = list;
    }

    @Override
    public void run() {
   
        while(true){
   
            synchronized (LOCK){
   
                if (list.isEmpty()) {
   
                    break;
                }
                else {
   
                    Collections.shuffle(list);
                    int value = list.removeFirst();
                    System.out.println(Thread.currentThread().getName()+"又产生一个"+value+"大奖");
                }
            }
            try {
   
                Thread.sleep(10);
            } catch (InterruptedException e) {
   
                throw new RuntimeException(e);
            }
        }
    }
}

测试类

public class MyThreadTestDemo5 {
   
    public static void main(String[] args) {
   
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        MyThreadFive t1 = new MyThreadFive(list);
        MyThreadFive t2 = new MyThreadFive(list);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}

6.多线程练习6

在上一题基础上继续完成如下需求:

​ 每次抽的过程中,不打印,抽完时一次性打印(随机)

​ 在此次抽奖过程中,抽奖箱1总共产生了6个奖项。

​ 分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元

在此次抽奖过程中,抽奖箱2总共产生了6个奖项。

​ 分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元

Thread中的Run方法

public class MyThreadSix extends Thread{
   
    //共享数据
    ArrayList<Integer> list;

    static final Object LOCK = new Object();

    ArrayList<Integer> list1 = new ArrayList<>(); //抽奖箱1
    ArrayList<Integer> list2 = new ArrayList<>(); //抽奖箱2

    public MyThreadSix(ArrayList<Integer> list) {
   
        this.list = list;
    }

    @Override
    public void run() {
   
        while(true){
   
            synchronized (LOCK){
   
                if (list.isEmpty()) {
   
                    if("抽奖箱1".equals(getName())){
   
                        int sum = 0;
                        System.out.println("抽奖箱1总共产生了"+list1.size()+"个奖项");
                        System.out.print("分别为");
                        System.out.print(list1);
                        System.out.println("最高为"+Collections.max(list1));
                        for (Integer i : list1) {
   
                            sum+=i;
                        }
                        System.out.println("总计为"+sum);
                    }
                    else if("抽奖箱2".equals(getName())){
   
                        int sum = 0;
                        System.out.println("抽奖箱2总共产生了"+list2.size()+"个奖项");
                        System.out.print("分别为");
                        System.out.print(list2);
                        System.out.println("最高为"+Collections.max(list2));
                        for (Integer i : list2) {
   
                            sum+=i;
                        }
                        System.out.println("总计为"+sum);
                    }
                    break;
                }
                else {
   
                    Collections.shuffle(list);
                    int value = list.removeFirst();
                    if("抽奖箱1".equals(getName())){
   
                        list1.add(value);
                    }
                    else if("抽奖箱2".equals(getName())){
   
                        list2.add(value);
                    }
                }
            }
            try {
   
                Thread.sleep(10);
            } catch (InterruptedException e) {
   
                throw new RuntimeException(e);
            }
        }
    }
}

测试类

public class MyThreadTestDemo6 {
   
    public static void main(String[] args) {
   
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        MyThreadSix t1 = new MyThreadSix(list);
        MyThreadSix t2 = new MyThreadSix(list);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}

以上的代码并不利于维护,我们可以利用线程栈的思想来解决这个问题,每个线程在执行时都会得到一个listBox的局部变量存放在栈中

public class MyThreadSix extends Thread {
   
    //共享数据
    ArrayList<Integer> list;

    static final Object LOCK = new Object();

    public MyThreadSix(ArrayList<Integer> list) {
   
        this.list = list;
    }

    @Override
    public void run() {
   
        ArrayList<Integer> listBox = new ArrayList<>();
        while (true) {
   
            synchronized (LOCK) {
   
                if (list.isEmpty()) {
   
                    int sum = 0;
                    System.out.println(getName() + "总共产生了" + listBox.size() + "个奖项");
                    System.out.print("分别为");
                    System.out.print(listBox);
                    System.out.println("最高为" + Collections.max(listBox));
                    for (Integer i : listBox) {
   
                        sum += i;
                    }
                    System.out.println("总计为" + sum);

                    break;
                } else {
   
                    Collections.shuffle(list);
                    int value = list.removeFirst();
                    listBox.add(value);
                }
            }
            try {
   
                Thread.sleep(10);
            } catch (InterruptedException e) {
   
                throw new RuntimeException(e);
            }
        }
    }
}

7.多线程练习7

在上一题基础上继续完成如下需求:

在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300

最高奖项为300元,总计额为932元

在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5,50,200,800,80,700

最高奖项为800元,总计额为1835元

在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元

以上打印效果只是数据模拟,实际代码运行的效果会有差异

思路:本题与前几题不同的地方在于,需要比较不同线程的执行结果,我们可以实现一个 Callable接口,来获取线程执行的返回值

实现Callable接口

public class MyCallable implements Callable<Integer> {
   

    ArrayList<Integer> list;

    static final Object LOCK = new Object();

    public MyCallable(ArrayList<Integer> list) {
   
        this.list = list;
    }

    @Override
    public Integer call() throws Exception {
   
        ArrayList<Integer> listBox = new ArrayList<>();
        while (true) {
   
            synchronized (LOCK) {
   
                if (list.isEmpty()) {
   
                    int sum = 0;
                    System.out.println(Thread.currentThread().getName() + "总共产生了" + listBox.size() + "个奖项");
                    System.out.print("分别为");
                    System.out.print(listBox);
                    System.out.println("最高为" + Collections.max(listBox));
                    for (Integer i : listBox) {
   
                        sum += i;
                    }
                    System.out.println("总计为" + sum);
                    break;
                } else {
   
                    Collections.shuffle(list);
                    int value = list.removeFirst();
                    listBox.add(value);
                }
            }
            Thread.sleep(10);
        }
        return Collections.max(listBox); //返回最大值
    }
}

测试类

public class Demo {
   
    public static void main(String[] args) throws ExecutionException, InterruptedException {
   
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        MyCallable mc = new MyCallable(list);

        FutureTask<Integer> ft1 = new FutureTask<>(mc);
        FutureTask<Integer> ft2 = new FutureTask<>(mc);

        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);

        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        t1.start();
        t2.start();

        System.out.println("其中最高奖项为"+Math.max(ft1.get(), ft2.get()));

    }
}

8.线程池

之前写多线程的弊端

​ 弊端1:用到线程的时候就创建

​ 弊端2:用完后线程就消失

线程池主要核心原理

​ ①创建一个池子,池子中是空的

​ ②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接 复用已有的线程即可

​ ③但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

线程池的代码实现

​ 1.创建线程池

​ 2.提交任务

​ 3.所有的任务全部执行完毕,关闭线程池

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象

public static ExecutorService newCachedThreadPool() //创建一个没有上限的线程池

public static ExecutorService newFixedThreadPool(int nThreads) //创建有上限的线程池

创建一个上限为3的线程池,其线程数最大为3

public class ThreadPoolDemo1 {
   
    public static void main(String[] args) {
   
        ExecutorService pool1 = Executors.newFixedThreadPool(3);
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
    }
}
public class MyRunnable implements Runnable{
   
    @Override
    public void run() {
   
        System.out.println(Thread.currentThread().getName());
    }
}

自定义线程池

核心线程数量(不能小于0)

线程池中最大线程的数量(最大数量>=核心线程数量)

空闲时间(值)(不能小于0)

空闲时间(单位)(用TimeUnit指定)

阻塞队列(不能为null)

创建线程的方式(不能为null)

要执行的任务过多时的解决方案(不能为null)

public class ThreadPoolDemo2 {
   
    public static void main(String[] args) {
   
        //ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
        //(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                3,//核心线程数量,不能小于0
                6,//最大线程数,不能小于0,最大数量 >= 核心线程数量
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
        );
    }
}

计算电脑CPU的最大并行数

最大并行数:cpu多同时运的线程

public class ThreadPoolDemo3 {
   
    public static void main(String[] args) {
   
        int count = Runtime.getRuntime().availableProcessors();
        System.out.println(count);
    }
}

线程池多大合适?

CPU 密集型运算: 最大并行数+1

I/O 密集型运算 : 最大并行数 期望CPU利用率 总时间(CPU计算时间 + 等待时间) / CPU计算时间

目录
相关文章
|
Java Spring 容器
@Resource 这个注解什么用啊
@Resource 这个注解什么用啊
994 0
|
存储 Java 数据库连接
MyBatis-Plus 基础操作指南:实现高效的增删改查
MyBatis-Plus 基础操作指南:实现高效的增删改查
1026 0
|
SQL 数据安全/隐私保护 索引
SQL语句速成
《SQL语句速成》由blue编写,涵盖建表、插入、查询、更新、删除、视图创建、权限管理及索引操作等核心内容。通过具体示例介绍SQL基本语法和常用聚合函数,帮助读者快速掌握SQL编程技巧。发布于2024年7月19日。
359 7
|
SQL 关系型数据库 MySQL
菜鸟之路Day30一一MySQL之DML&DQL
本文介绍了MySQL中DML(数据操作语言)和DQL(数据查询语言)的核心用法。DML主要包括插入(insert)、更新(update)和删除(delete)语句,通过具体示例演示了如何对表数据进行增删改操作。DQL则聚焦于数据查询,涵盖基本查询、条件查询、聚合函数、分组查询、排序查询和分页查询等内容。文章通过丰富的SQL语句实例,帮助读者掌握如何高效查询和操作数据库中的数据,适合初学者学习和实践。
621 12
|
Java 程序员
菜鸟之路Day22一一反射与动态代理
本文介绍了Java反射机制和动态代理的基本概念及应用。反射允许编程访问类的成员变量、构造方法和成员方法,通过三种方式获取Class对象,并演示了如何使用反射创建对象、调用方法和修改字段值。动态代理则通过接口实现无侵入式功能增强,展示了如何利用`Proxy`类和`InvocationHandler`接口生成代理对象并拦截方法调用。结合实例代码,详细讲解了反射在实际开发中的应用场景,如保存对象信息到文件和根据配置文件动态创建对象。 反射的主要作用包括: 1. 获取类的所有信息。 2. 结合配置文件动态创建对象。 动态代理的核心优势在于能够在不修改原有代码的情况下,为对象添加额外功能。
267 0
|
前端开发 Java
表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
文章通过一个表白墙/留言墙的初级SpringBoot项目实例,详细讲解了如何进行前后端开发,包括定义前后端交互接口、创建SpringBoot项目、编写前端页面、后端代码逻辑及实体类封装的全过程。
613 3
表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
10月前
|
安全 Java API
Java日期时间API:从Date到Java.time
本文深入解析了Java 8中引入的全新日期时间API,涵盖LocalDate、LocalTime、LocalDateTime、ZonedDateTime等核心类的使用,以及时间调整、格式化、时区处理和与旧API的互操作。通过实例对比,展示了新API在可变性、线程安全与易用性方面的显著优势,并提供迁移方案与实战技巧,助你掌握现代Java时间处理的最佳实践。
|
SQL XML Java
菜鸟之路Day35一一Mybatis之XML映射与动态SQL
本文介绍了MyBatis框架中XML映射与动态SQL的使用方法,作者通过实例详细解析了XML映射文件的配置规范,包括namespace、id和resultType的设置。文章还对比了注解与XML映射的优缺点,强调复杂SQL更适合XML方式。在动态SQL部分,重点讲解了`&lt;if&gt;`、`&lt;where&gt;`、`&lt;set&gt;`、`&lt;foreach&gt;`等标签的应用场景,如条件查询、动态更新和批量删除,并通过代码示例展示了其灵活性与实用性。最后,通过`&lt;sql&gt;`和`&lt;include&gt;`实现代码复用,优化维护效率。
1255 5
|
存储 Java 程序员
菜鸟之路Day26一一Maven
本文由blue撰写,发布于2025年3月25日,主要介绍Maven工具的使用。Maven是Apache旗下的开源项目,用于管理和构建Java项目,基于项目对象模型(POM)概念。文章详细讲解了Maven的安装配置、IDEA中集成Maven的方法、依赖管理(包括依赖配置、传递与排除、依赖范围)、以及Maven的生命周期(clean、default、site)。通过学习,读者可掌握Maven的基本功能及其在项目中的应用。
627 12
|
Java 程序员 索引
菜鸟之路Day14一一异常与File
此篇博客详细介绍了Java中的异常处理机制和File类的常用操作。
260 2