06.JAVAEE之线程4

news2024/12/23 23:49:50

1.定时器

1.1 定时器是什么

定时器也是软件开发中的一个重要组件.
类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定好的代码.
约定一个时间,时间到达之后,执行某个代码逻辑,
定时器非常常见,尤其是在进行网络通信的时候,

 需要有等待的最大时间,等待的最大时间通过定时器实现。

在标准库里,也是有现成的定时器的实现的 

主线程执行 schedule 方法的时候,就是把这个任务给放到 timer 对象中了,
于此同时,timer 里头也包含一个线程,这个线程叫做"扫描线程”,一旦时间到,扫描线程就会执行刚才安排的任务了。
仔细观察,可以发现,整个进程其实没有结束!! 就是因为 Timer 内部的线程,阻止了进程结束.
Timer 里,是可以安排多个任务的
import java.util.Timer;
import java.util.TimerTask;

// 定时器
public class Demo25 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        // 给定时器安排了一个任务, 预定在 xxx 时间去执行.
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        }, 3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        }, 1000);
        System.out.println("程序启动!");
    }
}

 1.2 如何实现定时器

Timer timer = new Timer();

1.Timer 中需要有一个线程,扫描任务是否到时间,可以执行了

2.需要有一个数据结构,把所有的任务都保存起来.

【具体使用什么数据结构好呢?

假设使用数组(ArrayList),此时,扫描线程, 就需要不停的遍历数组中的每个任务判定每个任务是否都到达执行时间.

使用优先级队列,是更好的办法!!给 Timer 中添加的这些任务, 都是带有一个"时间’定是时间小的先执行。最先执行的就是时间最小的任务!!如果时间最小的任务,还没到时间呢,其他任务更不会到时间了!!优先级队列,可以使用 O(1)时间,来获取到时间最小的任务的!!

3.还需要创建,个类,通过类的对象来描述一个任务.(至少要包含任务内容和时间)

使用绝对的时间戳更为方便

对于优先级队列,要求里面的元素是可比较的,所以需要重写比较方法。

  • 如果发现队列为空,应该咋办呢?

好的办法,就是阻塞等待,等到队列不空为止 =>阻塞队列不就是这样的嘛~~
wait 要想使用, 需要搭配 synchronized,不能单独使用!!wait 进行的操作有三个:
1)释放锁 =>前提是, 先拿到锁,
2)等待通知
3) 通知到来之后, 唤醒,重新获取锁
这个方法,是一个线程中(比如主线程中),给队列添加元素

出现忙等时

忙等的过程,确实在等,但是也消耗了很多 cpu

1个notify起到两个xiaog

之所以咱们的代码,使用的是 PriorityQueue,而不是 PriorityBlockingQueue,其实就是因为要处理两个 wait 的地方使用阻塞版本的优先级队列,不方便实现这样的两处等待~~

mport java.util.PriorityQueue;

// 通过这个类, 描述了一个任务
class MyTimerTask implements Comparable<MyTimerTask> {
    // 要有一个要执行的任务
    private Runnable runnable;
    // 还要有一个执行任务的时间
    private long time;

    // 此处的 delay 就是 schedule 方法传入的 "相对时间"
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        // 这样的写法, 就是让队首元素是最小时间的值
        // 到底是谁 - 谁, 不要背!! 你可以试试!!
        return (int) (this.time - o.time);
        // 如果是想让队首元素是最大时间的值
        // return o.time - this.time;
    }

    public long getTime() {
        return time;
    }

    public Runnable getRunnable() {
        return runnable;
    }
}

// 咱们自己搞的定时器
class MyTimer {
    // 使用一个数据结构, 保存所有要安排的任务.
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    // 使用这个对象作为锁对象.
    private Object locker = new Object();

    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            queue.offer(new MyTimerTask(runnable, delay));
            locker.notify();
        }
    }

    // 搞个扫描线程.
    public MyTimer() {
        // 创建一个扫描线程
        Thread t = new Thread(() -> {
            // 扫描线程, 需要不停的扫描队首元素, 看是否是到达时间.
            while (true) {
                try {
                    synchronized (locker) {
                        // 不要使用 if 作为 wait 的判定条件, 应该使用 while
                        // 使用 while 的目的是为了在 wait 被唤醒的时候, 再次确认一下条件.
                        while (queue.isEmpty()) {
                            // 使用 wait 进行等待.
                            // 这里的 wait, 需要由另外的线程唤醒.
                            // 添加了新的任务, 就应该唤醒.
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        // 比较一下看当前的队首元素是否可以执行了.
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            // 当前时间已经达到了任务时间, 就可以执行任务了
                            task.getRunnable().run();
                            // 任务执行完了, 就可以从队列中删除了.
                            queue.poll();
                        } else {
                            // 当前时间还没到任务时间, 暂时不执行任务.
                            // 暂时先啥都不干, 等待下一轮的循环判定了.
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

public class Demo26 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        }, 3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        }, 2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        }, 1000);
        System.out.println("程序开始执行");
    }
}

 2.线程池

线程诞生的意义,是因为进程的创建/销毁, 太重量了(比较慢)有对比,才有伤害,和进程比,线程,是更快了,但是如果进一步提高创建销毁的频率, 线程的开销也不能忽视了!

两种典型的办法,进一步提高效率:

1.协程(轻量级线程)

相比于线程,把系统调度的过程,给省略了.(程序猿手工调度当下,一种比较流行的并发编程的手段. 但是在 Java 圈子里,协程还不够流行.

2.线程池

线程池最大的好处就是减少每次启动、销毁线程的损耗。
在使用第一个线程的时候,提前把 2345..线程创建好 (培养感情)
后续如果想使用新的线程,不必重新创建了,直接拿过来就能用!!!(此时创建线程的开销就被降低了)

2.1 线程池的使用 

把线程创建好,放在池子里,后续用的时候直接从池子里来取~~

为什么从池子取, 的效率比新创建线程,效率更高??? 

从池子取,这个动作,是纯粹用户态的操作.

创建新的线程,这个动作,则是需要 用户态 +内核态 相互配合,完成的操作

 出现的问题

很多时候 构造一个对象,希望有多种构造方式.
多种方式,就需要使用多个版本的构造方法来分别实现.
但是构造方法要求方法的名字必须是类名,不同的构造方法,就只能通过 重载 的方式来区分了.(重载 =>参数类型/个数 不同)

上面两个代码并没有构成重载,故编译失败。

工厂设计模式

解决方案:使用工厂设计模式

使用工厂设计模式,就能解决这个问题.
使用普通的方法,代替构造方法完成初始化工作.普通方法就可以使用方法的名字来区分了.也就不再收到重载的规则制约了
实践中, 一般单独搞一个类,给这个类搞一些静态方法, 由这样的静态方法负责构造出对象。

使用不同的方法名做出区分

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo27 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }
}

Executors 创建线程池的几种方式

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池. 
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer. 

上述这几个工厂方法生成的线程池,本质上都是对一个 类 进行的封装,ThreadPoolExecutor这个类,功能非常丰富,提供了很多参数,标准库上述的几个工厂方法,其实就是给这个类填写了不同的参数用来构造线程池了【Executors 本质上是 ThreadPoolExecutor 类的封装】

  • 不同的拒绝策略有不同的效果

使用线程池,需要设置线程的数目.数目设置多少合适??

ExecutorService service = Executors.newFixedThreadPool(4);

在接触到实际代码之前是无法确定的。

一个线程,执行的代码,主要有两类:
1.cpu 密集型: 代码里主要的逻辑是在进行 算术运算/逻辑判断

2.IO密集型: 代码里主要进行的是 Io操作,
假设一个线程的所有代码都是 cpu 密集型代码,这个时候,线程池的数量不应该超过 N(设置 N 就是极限了)设置的比 N 更大,这个时候,也无法提高效率了.(cpu 吃满了)此时更多的线程反而增加调度的开销.
假设一个线程的所有代码都是 Io密集的,这个时候不吃 CPU,此时设置的线程数,就可以是超过 N.较大的值一个核心可以通过调度的方式,来并发执行~

我们就可以知道:

代码不同, 线程池的线程数目设置就不同,无法知道一个代码,具体多少内容是 cpu 密集, 多少内容是Io密集

正确做法: 使用实验的方式,对程序进行性能测试,测试过程中尝试修改不同的线程池的线程数目,看哪种情况下,最符合要求

 2.2 线程池的实现

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPool {
    // 任务队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

    // 通过这个方法, 把任务添加到队列中
    public void submit(Runnable runnable) throws InterruptedException {
        // 此处咱们的拒绝策略, 相当于是第五种策略了. 阻塞等待~~ (这是下策)
        queue.put(runnable);
    }

    public MyThreadPool(int n) {
        // 创建出 n 个线程, 负责执行上述队列中的任务.
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                // 让这个线程, 从队列中消费任务, 并进行执行.
                try {
                    Runnable runnable = queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    }
}

public class Demo28 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            int id = i;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务: " + id);
                }
            });
        }
    }
}

主线:

线程概念 -> Thread 用法 ->线程安全问题 ->wait notify -> 线程案例

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

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

相关文章

xilinx Mailbox 中的ipi message地址计算方式

适用于openAmp mailbox ipi id对应的ipi message地址计算方式 官方openamp硬件配置解析 OpenAMP Base Hardware Configurations - Xilinx Wiki - Confluence openamp官方设备树 meta-openamp/meta-xilinx-tools/recipes-bsp/device-tree/files/zynqmp-openamp.dtsi at rel-v2…

政安晨:【Keras机器学习示例演绎】(十四)—— 用于弱光图像增强的零 DCE

目录 简介 下载 LOL 数据集 创建 TensorFlow 数据集 零 DCE 框架 了解光线增强曲线 DCE-Net 损失函数 色彩恒定损失 曝光损失 光照平滑度损失 空间一致性损失 深度曲线估计模型 训练 推论 测试图像推理 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点…

遥感卫星影像地表温度反演

近年来&#xff0c;随着遥感技术的不断发展和卫星遥感数据的广泛应用&#xff0c;遥感卫星地表温度反演成为了地球科学和环境监测领域的重要研究内容。地表温度反演是指利用遥感卫星数据推算地表温度的过程&#xff0c;其原理基于地物表面的辐射特征与温度之间的关系。本文将介…

Github2024-04-25 开源项目日报Top10

根据Github Trendings的统计,今日(2024-04-25统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目4非开发语言项目2TypeScript项目2PowerShell项目1C++项目1Dart项目1JavaScript项目1GPT4All: 在边缘运行开源大型语言模型 创建周期:…

C++:拷贝构造函数的初始化列表

拷贝构造函数的初始化列表是在拷贝构造函数的定义中出现的一组初始值&#xff0c;用于初始化新创建的对象的成员变量。它的语法是在构造函数的声明后面使用冒号&#xff08;:&#xff09;来开头&#xff0c;然后列出要初始化的成员变量和它们的初始值。初始化列表的优点在于它允…

从国内盲盒小程序看国外市场的发展机遇与挑战

近年来&#xff0c;盲盒小程序在国内市场迅速崛起&#xff0c;凭借其独特的营销模式和消费者体验&#xff0c;赢得了大量年轻消费者的喜爱。从国内盲盒小程序的发展中&#xff0c;我们可以窥见国外市场的一些发展机遇与挑战&#xff0c;为进军国际市场提供有益的参考。 首先&a…

[Swift]单元测试

编写单元测试是确保你的代码质量和功能正确性的重要步骤 一、编写单元测试的详细流程 1. 创建一个新的Xcode项目 如果你尚未创建一个项目&#xff0c;首先你需要在Xcode中创建一个新的iOS项目&#xff1a; 打开Xcode&#xff0c;选择“File” > “New” > “Project”…

ARM DMIPS算力说明

ARM DMIPS算力说明 ARM算力参考官网地址 https://en.wikipedia.org/wiki/List_of_ARM_processors Product familyARM architectureProcessorFeatureCache (I / D), MMUTypical MIPS MHzReferenceARM1ARMv1ARM1First implementationNoneARM2ARMv2ARM2ARMv2 added the MUL (mu…

一文带你了解MySQL的DCL语句

文章目录 ☃️DCL☃️管理用户☃️权限控制☃️DCL的重要性 欢迎来到 请回答1024 的博客 &#x1f34e;&#x1f34e;&#x1f34e;欢迎来到 请回答1024的博客 关于博主&#xff1a; 我是 请回答1024&#xff0c;一个追求数学与计算的边界、时间与空间的平衡&#xff0c;0与1的…

【MySQL】A01、性能优化-结构设计与配置

1、数据库设计原则 1.1、核心原则 不在数据库做运算; cpu计算务必移至业务层; 控制列数量(字段少而精,字段数建议在20以内); 平衡范式与冗余(效率优先&#xff1b;往往牺牲范式) 拒绝3B(拒绝大sql语句&#xff1a;big sql、拒绝大事务&#xff1a;big transaction、拒绝大…

【漏洞复现】Palo Alto Networks PAN-OS GlobalProtect 命令注入漏洞 CVE-2024-3400

漏洞描述 Palo Alto Networks PAN-OS GlobalProtect 命令 Palo Alto Networks PAN-OS GlobalProtect 是Palo Alto Networks 的一款防火墙产品。2024年4月12日,官方披露CVE-2024-3400 Palo Alto Networks GlobalProtect 命令注入漏洞。在特定PAN-OS版本和不同功能配置下,未经…

618哪些好物值得入手?收下这份必买清单!

很快618就要来啦&#xff0c;在这之前一定要好好做攻略。在这个特别的时刻&#xff0c;我们不仅有机会淘到心仪已久的商品&#xff0c;还能享受到超值的折扣和优惠。为了让大家的购物体验更加愉快&#xff0c;我特意整理了一份618必买清单&#xff0c;里面汇聚了各类热门好物&a…

【算法入门-Python】02_递归

一、递归 递归的两个特点&#xff1a;调用自身&#xff1b;结束条件。 def func1(x):print(x)func1(x-1)没有结束条件&#xff0c;si递归。不是递归。 def func2(x)&#xff1a;if x > 0:print(x)func2(x1)递归调用的x1&#xff0c;没有结束条件。不是递归 def func3(x)…

2024最新版本FL Studio21水果编曲软件破解版激活许可证

FL Studio 21水果编曲软件适合广泛的人群使用&#xff0c;包括但不限于以下几类人&#xff1a; 音乐制作人&#xff1a;对于专业的音乐制作人来说&#xff0c;FL Studio 21提供了全面的音乐制作工具和功能&#xff0c;能够满足他们在音乐创作、编曲、混音等方面的需求。他们可以…

VUE父组件向子组件传递值

创作灵感 最近在写一个项目时&#xff0c;遇到了这样的一个需求。我封装了一个组件&#xff0c;这个组件需要被以下两个地方使用&#xff0c;一个是搜索用户时用到&#xff0c;一个是修改用户信息时需要用到。其中&#xff0c;在搜索用户时&#xff0c;可以根据姓名或者账号进…

Git TortoiseGit 详细安装使用教程

前言 Git 是一个免费的开源分布式版本控制系统&#xff0c;是用来保存工程源代码历史状态的命令行工具&#xff0c;旨在处理从小型到非常大型的项目&#xff0c;速度快、效率高。《请查阅Git详细说明》。TortoiseGit 是 Git 的 Windows Shell 界面工具&#xff0c;基于 Tortoi…

Mybatis入门(入门案例,IDEA配置SQL提示,JDBC介绍,lombok介绍)

目录 一、Mybatis入门案例介绍整体步骤创建SpringBoot项目pom依赖准备测试数据新建实体类配置Mybatis数据库连接信息新建接口类,编写SQL代码单元测试 二、IDEA配置SQL提示三、JDBC是什么案例JDBC和Mybatis对比 四、数据库连接池介绍如何实现一个数据库连接池切换数据库连接池 五…

【SpringCloud】LoadBalance负载均衡服务调用快速入门

【SpringCloud】LoadBalance负载均衡服务调用快速入门 文章目录 【SpringCloud】LoadBalance负载均衡服务调用快速入门1. 概述2. 引入依赖3. 配置、验证3.1 配置3.2 验证 1. 概述 官网地址&#xff1a;点击跳转 Spring Cloud LoadBalancer 是由 SpringCloud 官方提供的一个开…

自动化测试用例设计

知人者智&#xff0c;自知者明。大家好&#xff0c;给大家分享一下关于自动化测试用例的设计心得&#xff0c;首先完整的熟悉业务是第一步要做的&#xff0c;不熟悉业务的前提下不会设计出高效且合理的用例&#xff0c;其次是我们要有明确的测试目标&#xff0c;确保我们写的每…

CentOS 7.9.2009 中 Docker 使用 GPU

一、安装nvidia驱动 1.1&#xff0c;查看显卡驱动 # 查看显卡型号 lspci | grep -i nvidia 1.2&#xff0c;进入 PCI devices &#xff0c;输入上一步查询到的 2204 1.3&#xff0c;进入 官方驱动 | NVIDIA&#xff0c;查询 Geforce RTX 3090 驱动并下载 1.4&#xff0c;禁用…