【JavaEE初阶】多线程初阶下部

文章目录

  • 前言
  • 一、volatile关键字
    • volatile 能保证内存可见性
  • 二、wait 和 notify
    • 2.1 wait()方法
    • 2.2 notify()方法
    • 2.3 notifyAll()方法
    • 2.4 wait 和 sleep 的对比(面试题)
  • 三、多线程案例
    • 单例模式
  • 四、总结-保证线程安全的思路
  • 五、对比线程和进程
  • 总结


前言

这篇博客博主开始讲述多线程的下部,上部在博主的上一篇博客,需要的柚柚们可以看看~(链接在这里点击即可)。


提示:以下是本篇文章正文内容

一、volatile关键字

volatile 能保证内存可见性

代码在写入 volatile 修饰的变量的时候

  • 改变线程工作内存中volatile变量副本的值
  • 将改变后的副本的值从工作内存刷新到主内存
    代码在读取 volatile 修饰的变量的时候
  • 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 从工作内存中读取volatile变量的副本

代码示例:
在这个代码中

  • 创建两个线程 t1 和 t2
  • t1 中包含⼀个循环, 这个循环以 flag == 0 为循环条件.
  • t2 中从键盘读入⼀个整数, 并把这个整数赋值给 flag.
  • 预期当用户输入非 0 的值的时候, t1 线程结束
static class Counter {public int flag = 0;
}
public static void main(String[] args) {Counter counter = new Counter();Thread t1 = new Thread(() -> {while (counter.flag == 0) {}System.out.println("循环结束!");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("输⼊⼀个整数:");counter.flag = scanner.nextInt();});t1.start();t2.start();}
  • t1读的是自己的工作内存中的内容
  • 当 t2 对 flag 变量进行修改, 此时 t1 感知不到 flag 的变化.
static class Counter {public volatile int flag = 0;
}
  • 此时当用户输入非零值时,t2将flag的值刷回内存,然后t1再从内存中重新读取flag的值,我们可以发现t1线程循环能够立即结束。

二、wait 和 notify

由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.

完成这个协调工作, 主要涉及到三个方法:

  • wait() / wait(long timeout): 让当前线程进入等待状态
  • notify() / notifyAll(): 唤醒在当前对象上等待的线程

注:
wait, notify, notifyAll 都是 Object 类的方法.

2.1 wait()方法

wait 做的事情:

  • 使当前执行代码的线程进行等待. (把线程放到等待队列中)
  • 释放当前的锁
  • 满足⼀定条件时被唤醒, 重新尝试获取这个锁

注:wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

wait 结束等待的条件:

  • 其他线程调用该对象的 notify/notifyAll 方法.
  • wait 等待时间超时 (wait 方法提供⼀个带有 timeout 参数的版本, 来指定等待时间).
  • 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常

代码示例:

public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println("等待中");object.wait();System.out.println("等待结束");}
}

2.2 notify()方法

notify 方法是唤醒等待的线程

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  • 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 “先来后到”)
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行
    完,也就是退出同步代码块之后才会释放对象锁.

代码示例:

  • 创建 WaitTask 类, 对应⼀个线程, run 内部循环调用 wait.
  • 创建 NotifyTask 类, 对应另⼀个线程, 在 run 内部调用一次 notify
  • 注意, WaitTask 和 NotifyTask 内部持有同⼀个 Object locker. WaitTask 和 NotifyTask 要想配合就需要搭配同⼀个 Object.
static class WaitTask implements Runnable {private Object locker;public WaitTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {while (true) {try {System.out.println("wait 开始");locker.wait();System.out.println("wait 结束");} catch (InterruptedException e) {e.printStackTrace();}}}}
}static class NotifyTask implements Runnable {private Object locker;public NotifyTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {System.out.println("notify 开始");locker.notify();System.out.println("notify 结束");}}
}public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(new WaitTask(locker));Thread t2 = new Thread(new NotifyTask(locker));t1.start();Thread.sleep(1000);t2.start();
}

2.3 notifyAll()方法

notify方法只是唤醒某⼀个等待线程. 使用notifyAll方法可以⼀次唤醒所有的等待线程(同一个对象锁的所有等待线程),唤醒之后需要重新竞争锁。

2.4 wait 和 sleep 的对比(面试题)

其实理论上 wait 和 sleep 完全是没有可比性的,因为⼀个是用于线程之间的通信的,⼀个是让线程阻塞⼀段时间,唯⼀的相同点就是都可以让线程放弃执行⼀段时间.
不同点:

  1. wait 需要搭配 synchronized 使用, sleep 不需要。
  2. wait 是 Object 的方法 sleep 是 Thread 的静态方法。

三、多线程案例

单例模式

单例模式是校招中最常考的设计模式之⼀,单例模式能保证某个类在程序中只存在唯⼀⼀份实例, 而不会创建出多个实例,单例模式具体的实现方式有很多. 最常见的是 “饿汉” 和 “懒汉” 两种。

那我们要如何保证一个程序中对象是单例的,用什么方法去保证?
1.人为口头约束,大家不要new这个对象,我给大家提供一个方法,这个方法可以返回一个单例的对象。(但是显然是不可取的,人和人之间的信任不太靠谱)
2.通过语言自身的语法约束,限制一个类只能被实例化一个对象。(把限制的过程交给程序,程序写死了就按照写的逻辑执行,不会改变)

实现过程:
1.要实现单例类,只需要定义一个static修饰的变量,就可以保证这个变量全局唯一(单例)

public class Singleton {private static Singleton instance = new Singleton();public Singleton getInstance() {return instance;}
}
class Test{public static void main(String[] args){Singleton instance1 = new Singleton();System.out.println(instance1.getInstance());Singleton instance2 = new Singleton();System.out.println(instance2.getInstance());Singleton instance3 = new Singleton();System.out.println(instance3.getInstance());}
}
  • private:防止外部对这个变量修改(就会导致instance不唯一)
  • static:保证全局唯一

输出:
在这里插入图片描述
从输出可以看出我们似乎实现啦单例,但是还是有不足的

2.既然是单例,就不想然外部去new这个对象,虽然返回的是同一个对象,已经实现了单例,但代码书写上有歧义,所以我们要将构造方法私有化,但是私有化之后外界就获取不到单例类了,所以我们要给getInstance方法前面加上static,我们就就可以通过类名.方法名的方式调用。修改之后的代码为:

public class Singleton {private static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance() {return instance;}
}
class Test{public static void main(String[] args){Singleton instance1 = Singleton.getInstance();System.out.println(instance1.getInstance());Singleton instance2 = Singleton.getInstance();System.out.println(instance2.getInstance());Singleton instance3 = Singleton.getInstance();System.out.println(instance3.getInstance());}
}

输出:
在这里插入图片描述
现在我们就真真正正地实现单例啦~

饿汉模式:类加载的同时, 创建实例

public class Singleton {private static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance() {return instance;}
}

懒汉模式-单线程版:类加载的时候不创建实例. 第⼀次使用的时候才创建实例

public class Singleton {private static Singleton instance = null;private Singleton(){}public static Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;}
}

懒汉模式-多线程版:上面的懒汉模式对于多线程的实现是线程不安全的

  • 线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例
  • ⼀旦实例已经创建好了, 后面再多线程环境调用getInstance 就不再有线程安全问题了(不再修改instance 了)
class Singleton {private static Singleton instance = null;private Singleton() {}public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

懒汉模式-多线程版(改进):以下代码在加锁的基础上, 做出了进⼀步改动
• 使用双重 if 判定, 降低锁竞争的频率.
• 给 instance 加上了 volatile.

class Singleton {private static volatile Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

四、总结-保证线程安全的思路

  1. 使用没有共享资源的模型
  2. 适用共享资源只读,不写的模型
    a. 不需要写共享资源的模型
    b. 使用不可变对象
  3. 直面线程安全(重点)
    a. 保证原子性
    b. 保证顺序性
    c. 保证可见性

五、对比线程和进程

5.1 线程的优点

  1. 创建⼀个新线程的代价要比创建⼀个新进程小得多
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  3. 线程占用的资源要比进程少很多
  4. 能充分利用多处理器的可并行数量
  5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

5.2 进程与线程的区别

  1. 进程是系统进行资源分配和调度的⼀个独立单位,线程是程序执行的最小单位。
  2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
  3. 由于同⼀进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
  4. 线程的创建、切换及终止效率更高。

总结

这篇博客博主后续还会更新更多案例,有兴趣地柚柚们可以点赞收藏,方便后续观看~

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

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

相关文章

使用 前端技术 创建 QR 码生成器 API1

前言 QR码(Quick Response Code)是一种二维码,于1994年开发。它能快速存储和识别数据,包含黑白方块图案,常用于扫描获取信息。QR码具有高容错性和快速读取的优点,广泛应用于广告、支付、物流等领域。通过扫…

vxe-modal VxeUI 窗口组件弹窗多窗口模式

VxeUI 实现在 vue 中使用弹窗组件,弹窗多个窗口可叠加,实现多实例的窗口组件。 npm install vxe-pc-ui4.3.6// ...import VxeUI from vxe-pc-uiimport vxe-pc-ui/lib/style.css// ...createApp(App).use(VxeUI).mount(#app)// ...官网:https…

无人机探测:光电侦测核心技术算法详解!

核心技术 双光谱探测跟踪: 可见光成像技术:利用无人机表面反射的自然光或主动光源照射下的反射光,通过高灵敏度相机捕捉图像。该技术适用于日间晴朗天气下的无人机探测,具有直观、易于识别目标的特点。 红外成像技术&#xff1…

【ArcGISPro】Sentinel-2数据处理

错误 默认拉进去只组织了4个波段,但是实际有12个波段 解决方案 数据下载 Sentinel-2 数据下载-CSDN博客 数据处理 数据查看 创建镶嵌数据集 在数据管理工具箱中找到创建镶嵌数据集

现代密码学

概论 计算机安全的最核心三个关键目标(指标)/为:保密性 Confidentiality、完整性 Integrity、可用性 Availability ,三者称为 CIA三元组 数据保密性:确保隐私或是秘密信息不向非授权者泄漏,也不被非授权者使…

Python绘制太极八卦

文章目录 系列目录写在前面技术需求1. 图形绘制库的支持2. 图形绘制功能3. 参数化设计4. 绘制控制5. 数据处理6. 用户界面 完整代码代码分析1. rset() 函数2. offset() 函数3. taiji() 函数4. bagua() 函数5. 绘制过程6. 技术亮点 写在后面 系列目录 序号直达链接爱心系列1Pyth…

uniapp vue2项目迁移vue3项目

uniapp vue2项目迁移vue3项目,必须适配的部分 一、main.js 创建应用实例 // 之前 - Vue 2 import Vue from vue import App from ./App Vue.config.productionTip false // vue3 不再需要 App.mpType app // vue3 不再需要 const app new Vue({ ...App }) …

卷积神经网络学习记录

目录 神经网络基础定义: 基本组成部分 工作流程 卷积层(卷积定义)【CONV】: 卷积层(Convolutional Layer) 特征提取:卷积层的主要作用是通过卷积核(或滤波器)运算提…

element-ui 中el-calendar 日历插件获取显示的第一天和最后一天【原创】

需要获取el-calendar 日历组件上的第1天和最后一天。可以通过document.querySelector()方法进行获取dom元素中的值,这样避免计算问题。 获取的过程中主要有两个难点,第1个是处理上1月和下1月的数据,第2个是跨年的数据。 直接贴代码&#xff…

一个高度可扩展的 Golang ORM 库【GORM】

GORM 是一个功能强大的 Golang 对象关系映射(ORM)库,它提供了简洁的接口和全面的功能,帮助开发者更方便地操作数据库。 1. 完整的 ORM 功能 • 支持常见的关系模型: • Has One(一对一) • …

【大数据学习 | Spark-Core】Spark的改变分区的算子

当分区由多变少时,不需要shuffle,也就是父RDD与子RDD之间是窄依赖。 当分区由少变多时,是需要shuffle的。 但极端情况下(1000个分区变成1个分区),这时如果将shuffle设置为false,父子RDD是窄依赖关系&…

IDEA2024如何创建Web项目以及配置Tomcat

在Web项目的开发过程中,Tomcat作为一款开源的Servlet容器,扮演着至关重要的角色。它不仅能够提供稳定的运行环境,还支持多种Java EE规范,为开发者提供了丰富的功能支持。因此,正确配置Tomcat服务器对于确保Web项目的顺…

【通俗理解】隐变量的变分分布探索——从公式到应用

【通俗理解】隐变量的变分分布探索——从公式到应用 关键词提炼 #隐变量 #变分分布 #概率模型 #公式推导 #期望最大化 #机器学习 #变分贝叶斯 #隐马尔可夫模型 第一节:隐变量的变分分布的类比与核心概念【尽可能通俗】 隐变量的变分分布就像是一场“捉迷藏”游戏…

亚信安全与飞书达成深度合作

近日,亚信安全联合飞书举办的“走近先进”系列活动正式走进亚信。活动以“安全护航信息化 共筑数字未来路”为主题,吸引了众多数字化转型前沿企业的近百位领导参会。作为“走近先进”系列的第二场活动,本场活动更加深入挖掘了数字化转型的基础…

【Vue】 npm install amap-js-api-loader指南

前言 项目中的地图模块突然打不开了 正文 版本太低了,而且Vue项目就应该正经走项目流程啊喂! npm i amap/amap-jsapi-loader --save 官方说这样执行完,就这结束啦!它结束了,我还没有,不然不可能记录这篇文…

使用ENSP实现默认路由

一、项目拓扑 二、项目实现 1.路由器AR1配置 进入系统试图 sys将路由器命名为R1 sysname R1关闭信息中心 undo info-center enable 进入g0/0/0接口 int g0/0/0将g0/0/0接口IP地址配置为2.2.2.1/24 ip address 2.2.2.1 24进入g0/0/1接口 int g0/0/1将g0/0/1接口IP地址配置为1.…

Ansible--自动化运维工具

Ansible自动化运维工具介绍 1.Ansible介绍 Ansible是一款自动化运维工具,基于Python开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。…

WSL安装不同版本ubuntu(已有ubuntu20.04,再装ubuntu18.04)

参考: 如何在 WSL 中删除指定版本的 Ubuntu(以删除 Ubuntu 22.04 为例)_wsl卸载某个-CSDN博客 已有ubuntu20.04,现在再安装一个ubuntu18.04 直接参考下面我写的链接的第四步,前面的步骤都不需要再做了 Win11安装WSL…

深度学习:GPT-1的MindSpore实践

GPT-1简介 GPT-1(Generative Pre-trained Transformer)是2018年由Open AI提出的一个结合预训练和微调的用于解决文本理解和文本生成任务的模型。它的基础是Transformer架构,具有如下创新点: NLP领域的迁移学习:通过最…

websocket是什么?

一、定义 Websocket是一种在单个TCP连接上进行全双工通信的协议,它允许服务器主动向客户端推送数据,而不需要客户端不断的轮询服务器来获取数据 与http协议不同,http是一种无状态的,请求,响应模式的协议(单向通信)&a…