多线程(代码案例: 单例模式, 阻塞队列, 生产者消费者模型,定时器)

设计模式是什么

类似于棋谱一样的东西
计算机圈子里的大佬为了能让小菜鸡的代码不要写的太差
针对一些典型的场景, 给出了一些典型的解决方案
这样小菜鸡们可以根据这些方案(ACM里面叫板子, 象棋五子棋里叫棋谱, 咱这里叫 设计模式), 略加修改, 这样代码再差也差不到哪里去 …

单例模式

单例模式 => 单个对象(实例)
在有些场景中, 有些特定的类, 只允许创建出一个实例, 不应该创建出多个实例
单例模式就是巧用 Java 的语法规则, 达成了某个类只能被创建出一个实例 (单线程多线程下都只能创建出一个实例)


单例模式的实现 – 饿汉模式

// 单例模式 - 饿汉模式
class Singleton {// 此处先创建出一个实例 (类加载阶段就创建出来了)private static Singleton singleton = new Singleton();// 如果使用该唯一实例, 统一通过 Singleton.getSingleton() 方法使用public static Singleton getSingleton() {return singleton;}// 将构造方法设置为私有, 即不可再创建实例private Singleton() {}
}
public class Main{public static void main(String[] args) {Singleton s = Singleton.getSingleton();Singleton ss = Singleton.getSingleton();System.out.println("s == ss : " + (s==ss));}
}

运行结果

在这里插入图片描述


单例模式的实现 – 懒汉模式

// 单例模式 - 懒汉模式
class SingletonLazy {volatile private static SingletonLazy singletonLazy = null; //volatile 保证内存可见性, 即public static SingletonLazy getInstance() {if(singletonLazy == null) { //判断是否要加锁synchronized (SingletonLazy.class) {if(singletonLazy == null) { //判断是否要创建对象singletonLazy = new SingletonLazy();}}}return singletonLazy;}private SingletonLazy() {}
}public class Main {public static void main(String[] args) {SingletonLazy s = SingletonLazy.getInstance();SingletonLazy ss = SingletonLazy.getInstance();System.out.println("s == ss : " + (s == ss));}
}

运行结果
在这里插入图片描述

这段代码挺有意思的, 其中值得关注的点挺多
双层 if :

  1. 外层 if 判断里面的 sychronized ,加锁操作是否要执行, 如果 singletonLazy 对象已存在, 就不用再进行加锁,创建对象的操作了 (sychronized 操作比 if 操作消耗要多的多, 如果不追求性能, 外层 if 可以不要)
  2. 内层 if 判断是否要创建对象, 如果 singletonLazy 未存在, 就创建. 多线程环境下可能会出现同时创建多个对象的情况 (不满足单例模式的要求), 因此我们对内层 if 判断及里面的创建对象进行加锁, 由于是单例模式 (只有一个类对象), 因此直接对该类对象加锁就好

volatile 保证内存可见性以及禁止指令重排序, 这也是对内层 if :if(singletonLazy == null)的限制, 防止多线程环境下出现 “类似脏读的问题”

警告: sychronized 能够保证原子性, 但是 sychronized 能否保证 内存可见性, 这里是存疑的 (有的资料说 sychronized 不能保证内存可见性, 因此保险起见, 这里是 volatile 也加上的 …)


懒汉模式和饿汉模式的区别

懒汉模式就是不直接创建对象(实例), 什么时候用到, 才创建对象(实例)
饿汉模式就是直接创建对象, 需要用的时候可以直接用, 不用再等待实例的创建等过程


阻塞队列

阻塞队列, 也是队列, 因此具有特点 – 先进先出, 后进后出
阻塞:

  1. 如果队列为空,执行出队列操作, 就会阻塞, 直到其他线程往队列里添加元素 (队列不空)
  2. 如果队列已满,执行入队列操作, 也会阻塞, 直到其他线程从队列里取走元素 (队列不满)

Java 标准库实现的阻塞队列

public class Main{public static void main(String[] args) throws InterruptedException {BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();blockingQueue.put("hello");System.out.println(blockingQueue.take());// 其实在多线程环境下使用阻塞效果更明显, 这里只是单纯当初队列来用了}
}

运行结果
在这里插入图片描述


手写阻塞队列

不考虑泛型, 单纯考虑队列中元素为 Integer 类型, 使用数组实现循环队列

// 手写阻塞队列 (不考虑泛型, 单纯的 Integer 类型, 循环数组实现)
class MyBlockingQueue {private int[] items = new int[1000];private int head = 0; //头指针private int tail = 0; //尾指针private int size = 0; //已有元素的数量// 入队列public void put(int value) throws InterruptedException {synchronized (this) {while(size == items.length) { // while 是精髓, 如果 put 操作被唤醒后, 又因某些原因队列又满了, 这里的 while 可以达到多次判断的效果(这也是 Java 标准库阻塞队列的写法)this.wait();}items[tail] = value;tail++;if(tail >= items.length) tail = 0;size++;this.notify();}}// 出队列public Integer take() throws InterruptedException {int val;synchronized (this) {while (this.size == 0) { //这个 while 的作用同上this.wait();}val = items[head];head++;if(head >= items.length) {head = 0;}size--;this.notify();}return val;}
}public class Main{public static void main(String[] args) throws InterruptedException {MyBlockingQueue queue = new MyBlockingQueue();Thread t1 = new Thread(() -> {try {System.out.println(queue.take());} catch (InterruptedException e) {e.printStackTrace();}});Thread t2 = new Thread(() -> {try {queue.put(10);} catch (InterruptedException e) {e.printStackTrace();}});t1.start();Thread.sleep(3000); //这里是为了让 t1线程先执行, 让队列阻塞掉 (如果 t2 先执行, 那么 t1 执行的时候, 队列内就会有数据, 就不会产生阻塞的效果了) (不加 Thread.sleep(3000) 的话, t1 和 t2 谁先执行, 是不一定的[该死的随机调度, 抢占式执行 ...])t2.start();t1.join();t2.join();}
}

运行结果
在这里插入图片描述


生产者消费者模型

作用

  1. 实现发送方和接收方的解耦合
  2. 削峰填谷

其实就是阻塞队列的简单使用

// 简单实现生产者消费者模型
public class Main {public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();// 消费者Thread customer = new Thread(() -> {while(true) {try {System.out.println("消费元素 " + blockingQueue.take());} catch (InterruptedException e) {e.printStackTrace();}}});// 生产者Thread producer = new Thread(() -> {int x = 0;while(true) {try {blockingQueue.put(x);System.out.println("生产元素 " + x++);Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}});customer.start();producer.start();customer.join();producer.join();}
}

运行结果
在这里插入图片描述

这里代码未结束, 而是一直执行下去, 并且代码逻辑是先生产, 再消费


定时器

作用

让一个任务在指定时间运行


Java 标准库提供了 “定时器”

// 定时器的简单使用
public class Main{public static void main(String[] args) {System.out.println("程序启动");Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("定时器1 任务执行");}}, 1000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("定时器2 任务执行");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("定时器3 任务执行");}}, 3000);}
}

运行结果
在这里插入图片描述


手写一个定时器

定时器的核心:

  1. 有一个扫描线程, 当 任务时间到 的时候执行任务
  2. 有一个数据结构来被注册任务

数据结构使用阻塞优先级队列 (可保证线程安全), 也可根据任务的执行时间进行排序
每个任务包含两部分 (任务的内容, 执行时间)

// 手写定时器// 任务
class MyTask implements Comparable<MyTask> {// 要执行的任务private Runnable runnable;// 要执行的时间private long time;public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}// 获取任务的执行时间public long getTime() {return time;}// 执行任务public void run() {runnable.run();}// 这里定义了 阻塞优先级队列 的排序规则, 别死记, 当场试一试@Overridepublic int compareTo(MyTask o) {return (int)(this.time - o.time);}
}// 定时器
class MyTimer {// 扫描线程private Thread t = null;// 使用阻塞优先队列, 来保存任务private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();public MyTimer() {// 只要调用了构造方法,即创建了定时器, 扫描器就会一直扫任务队列, 看是否有任务需要执行t = new Thread(() -> {while(true) {try {synchronized (this) {// 取出队首元素, 判断是否任务时间已到MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if(curTime < myTask.getTime()) {// 任务时间未到, 丢回任务队列queue.put(myTask);this.wait(myTask.getTime() - curTime); //这里的设计很巧妙, wait 既保证可以在当前队列最早的任务可以及时执行, 当新的任务来临时 notify 也可以将扫描线程唤醒, 也防止了扫描线程一直扫占用 CPU 资源(忙等)} else {myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}public void schedule(Runnable runnable, long after) {MyTask myTask = new MyTask(runnable, (after + System.currentTimeMillis()) );queue.put(myTask);synchronized (this) {this.notify(); //这里唤醒的作用是, 如果当前塞进去的任务的执行时间要先于当前队列中任务执行时间最近的那个, 即可以优先执行本任务}}
}public class Main {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello MyTimer!");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello MyTimer2!");}}, 3000);}
}

运行结果
在这里插入图片描述


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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#装箱和拆箱

一&#xff0c;装箱 装箱是指将值类型转化为引用类型。 代码如下&#xff1a; 装箱的内部过程 当值类型需要被装箱为引用类型时&#xff0c;CLR&#xff08;Common Language Runtime&#xff09;会为值类型分配内存&#xff0c;在堆上创建一个新的对象。值类型的数据会被复…

Flask智慧农业农产品价格可视化

Flaskecharts做多图联动查询界面&#xff1a; 引言 目的和背景文档结构 农产品价格数据/空气质量指数AQI可视化展示分析 数据收集和准备 数据源描述数据收集方法数据清洗与预处理 可视化界面设计与实现 界面需求描述使用echarts和pyecharts实现可视化界面输入参数和数据展示方…

【Kubernetes】k8s删除master节点后重新加入集群

目录 前言一、思路二、实战1.安装etcdctl指令2.重置旧节点的k8s3.旧节点的的 etcd 从 etcd 集群删除4.在 master03 上&#xff0c;创建存放证书目录5.把其他控制节点的证书拷贝到 master01 上6.把 master03 加入到集群7.验证 master03 是否加入到 k8s 集群&#xff0c;检查业务…

Milvus向量数据库检索

官方文档&#xff1a;https://milvus.io/docs/search.md   本节介绍如何使用 Milvus 搜索实体。   Milvus 中的向量相似度搜索会计算查询向量与具有指定相似度度量的集合中的向量之间的距离&#xff0c;并返回最相似的结果。您可以通过指定过滤标量字段或主键字段的布尔表达…

sql join

-- 创建事实表 CREATE TABLE product_facts (id INT AUTO_INCREMENT PRIMARY KEY,product_name VARCHAR(255),price DECIMAL(10, 2) );-- 插入数据 INSERT INTO product_facts (product_name, price) VALUES (Product A, 100.00); INSERT INTO product_facts (product_name, pr…

Spring Boot(六十九):利用Alibaba Druid对数据库密码进行加密

1 Alibaba Druid简介 之前介绍过Alibaba Druid的,章节如下,这里就不介绍了: Spring Boot(六十六):集成Alibaba Druid 连接池 这章使用Alibaba Druid进行数据库密码加密,在上面的代码上进行修改,这章只介绍密码加密的步骤。 目前越来越严的安全等级要求,我们在做产品…

百科源码生活资讯百科门户类网站百科知识,生活常识

百科源码生活资讯百科门户类网站百科知识,生活常识 百科源码安装环境 支持php5.6&#xff0c;数据库mysql即可&#xff0c;需要有子目录权限&#xff0c;没有权限的话无法安装 百科源码可以创建百科内容&#xff0c;创建活动内容。 包含用户注册&#xff0c;词条创建&#xff…

使用 opencv 识别答题卡,生成填涂答案

一般答题卡设计时都在试卷4个角预留4个一样大小的黑块 仅能识别选择题判断题之类的填涂答题的题目&#xff0c;不能识别填空题应用题等其它主观题 使用 opencv 识别试卷图片中所有黑块&#xff0c;再根据黑块大小获取四个角的位置&#xff0c;根据四个黑块位置校正图像 将图…

Ansible非标记语言YAML与任务剧本Playbook

前言 上篇介绍了 Ansible 单模块&#xff08;AD-Hoc&#xff09;的相关内容Ansible自动化运维Inventory与Ad-Hoc-CSDN博客&#xff0c;Ad-Hoc 命令是一次性的、即时执行的命令&#xff0c;用于在远程主机上执行特定任务&#xff0c;这些命令通常用于快速执行简单的任务。当需要…

Spring Boot 集成 WebSocket 实例 | 前端持续打印远程日志文件更新内容(模拟 tail 命令)

这个是我在 CSDN 的第一百篇原则博文&#xff0c;留念&#x1f60e; #1 需求说明 先说下项目结构&#xff0c;后端基于 Spring Boot 3&#xff0c;前端为 node.js 开发的控制台程序。现在希望能够在前端模拟 tail 命令&#xff0c;持续输出后端的日志文件。 #2 技术方案 #2.…

Python基础(七)之数值类型集合

Python基础&#xff08;七&#xff09;之数值类型集合 1、简介 集合&#xff0c;英文set。 集合&#xff08;set&#xff09;是由一个或多个元素组成&#xff0c;是一个无序且不可重复的序列。 集合&#xff08;set&#xff09;只存储不可变的数据类型&#xff0c;如Number、…

【mask】根据bbox提示同一张图片生成多个矩形框掩码

前提&#xff1a;使用labelimg得到bbox 1.代码 import cv2 import numpy as np# 读取图片 image cv2.imread("D:\Desktop\mult_test\images\SL03509990_1694761223500.jpg")# 假设我们有多个目标的ROI&#xff08;感兴趣区域&#xff09; rois [(565,635,1006,85…

MySQL实战:监控

监控指标 性能类指标 名称说明QPS数据库每秒处理的请求数量TPS数据库每秒处理的事务数量并发数数据库实例当前并行处理的会话数量连接数连接到数据库会话的数量缓存命中率Innodb的缓存命中率 功能类指标 名称说明可用性数据库是否正常对外提供服务阻塞当前是否有阻塞的会话…

zookeeper快速入门四:在java客户端中操作zookeeper

系列文章&#xff1a; zookeeper快速入门一&#xff1a;zookeeper安装与启动-CSDN博客 zookeeper快速入门二&#xff1a;zookeeper基本概念-CSDN博客 zookeeper快速入门三&#xff1a;zookeeper的基本操作 先启动zookeeper服务端。 在maven引入zookeeper依赖。 <depende…

51单片机—DS18B20温度传感器

目录 一.元件介绍及原理 二&#xff0c;应用&#xff1a;DS18B20读取温度 一.元件介绍及原理 1.元件 2.内部介绍 本次元件使用的是单总线 以下为单总线的介绍 时序结构 操作流程 本次需要使用的是SKIP ROM 跳过&#xff0c; CONVERT T温度变化&#xff0c;READ SCRATCHPAD…

RabbitMQ命令行监控命令详解

在分布式系统中&#xff0c;消息队列中间件如RabbitMQ扮演着至关重要的角色。为了保证系统的稳定性和高可用性&#xff0c;对RabbitMQ进行有效监控是必不可少的。本文将详细介绍RabbitMQ提供的命令行工具rabbitmqctl&#xff0c;这些工具可以帮助我们监控和管理RabbitMQ服务器。…

【论文精读】DDPM:Denoising Diffusion Probabilistic Models 去噪扩散概率模型

文章目录 一、背景&#xff08;一&#xff09;生成模型&#xff08;二&#xff09;数学理论基础&#xff08;三&#xff09;扩散模型的三种生成范式 二、文章概览&#xff08;一&#xff09;核心思想&#xff08;二&#xff09;前向过程&#xff08;三&#xff09;后向过程&…

玩转C语言——数组初探

一、前言 通过前面的学习&#xff0c;我们已了解C语言的结构变量、分支结构和循环结构。今天&#xff0c;我们一起来认识C语言的另一知识点——数组。先赞后看&#xff0c;养成习惯。 二、数组概念 学习数组&#xff0c;我们要明白数组是什么。在我看来&#xff1a;数组是⼀组…

jenkins 使用k8s插件连接k8s集群

jenkins 安装k8s 插件 配置k8s节点 填写k8s 配置信息 如果不是买的https证书 切记不检查https Kubenetes 服务证书key 的获取 登录 k8s 服务器 查看地址 Kubernetes 服务证书 key cat /root/..kube/config 查看秘钥 对秘钥进行base64 位 加密 echo "秘钥内容&…

JavaWeb后端——分层解耦 IOC DI

分层/三层架构概述 三层架构&#xff1a;Controller、Service、Dao 解耦/IOC&DI概述 分层解耦 容器称为&#xff1a;IOC容器/Spring容器 IOC 容器中创建&#xff0c;管理的对象&#xff0c;称为&#xff1a;bean 对象 IOC&DI入门 实现 IOC&DI 需要的注解&#…