【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.mzph.cn/diannao/61979.shtml

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

相关文章

【人工智能】Python在机器学习与人工智能中的应用

Python因其简洁易用、丰富的库支持以及强大的社区,被广泛应用于机器学习与人工智能(AI)领域。本教程通过实用的代码示例和讲解,带你从零开始掌握Python在机器学习与人工智能中的基本用法。 1. 机器学习与AI的Python生态系统 Pyth…

“iOS profile文件与私钥证书文件不匹配”总结打ipa包出现的问题

目录 文件和证书未加载或特殊字符问题 证书过期或Profile文件错误 确认开发者证书和私钥是否匹配 创建证书选择错误问题 申请苹果 AppId时勾选服务不全问题 ​总结 在上线ios平台的时候,在Hbuilder中打包遇见了问题,生成ipa文件时候,一…

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

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

JavaScript的基础数据类型

一、JavaScript中的数组 定义 数组是一种特殊的对象,用于存储多个值。在JavaScript中,数组可以包含不同的数据类型,如数字、字符串、对象、甚至其他数组。数组的创建有两种常见方式: 字面量表示法:let fruits [apple…

5.5 W5500 TCP服务端与客户端

文章目录 1、TCP介绍2、W5500简介2.1 关键函数socketlistensendgetSn_RX_RSRrecv自动心跳包检测getSn_SR 1、TCP介绍 TCP 服务端: 创建套接字[socket]:服务器首先创建一个套接字,这是网络通信的端点。绑定套接字[bind]:服务器将…

Android 15 版本更新及功能介绍

Android 15版本时间戳 Android 15,代号Vanilla Ice Cream(香草冰淇淋),是当下 Android 移动操作系统的最新主要版本。 开发者预览阶段:2024年2月,谷歌发布了Android 15的第一个开发者预览版本(DP1),这标志着新系统开发的正式启动。随后,在3月和4月,谷歌又相继推出了D…

第02章_MySQL环境搭建(基础)

1. MySQL 的卸载 1.1 步骤1:停止 MySQL 服务 在卸载之前,先停止 MySQL8.0 的服务。按键盘上的 “Ctrl Alt Delete” 组合键,打开“任务管理器”对话 框,可以在“服务”列表找到“MySQL8.0” 的服务,如果现在“正在…

红队笔记--W1R3S、JARBAS、SickOS、Prime打靶练习记录

W1R3S(思路为主) 信息收集 首先使用nmap探测主机,得到192.168.190.147 接下来扫描端口,可以看到ports文件保存了三种格式 其中.nmap和屏幕输出的一样;xml这种的适合机器 nmap -sT --min-rate 10000 -p- 192.168.190.147 -oA nmapscan/ports…

学习笔记|MaxKB对接本地大模型时,选择Ollma还是vLLM?

在使用MaxKB开源知识库问答系统的过程中,除了对接在线大模型,一些用户出于资源配置、长期使用成本、安全性等多方面考虑,还在积极尝试通过Ollama、vLLM等模型推理框架对接本地离线大模型。而在用户实践的过程中,经常会对候选的模型…

计算机网络八股整理(一)

计算机网络八股文整理 一:网络模型 1:网络osi模型和tcp/ip模型分别介绍一下 osi模型是国际标准的网络模型,它由七层组成,从上到下分别是:应用层,表示层,会话层,传输层,…

Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序

在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序 IntelliJ IDEA 是一个用 Java 编写的集成开发环境 (IDE)。它用于开发计算机软件。此 IDE 由 Jetbrains 开发,提供 Apache 2 许可社区版和商业版。它是一种智能的上下文感知 IDE,可用于在各种应用程序…

单片机学习笔记 9. 8×8LED点阵屏

更多单片机学习笔记:单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…

vue 预览pdf 【@sunsetglow/vue-pdf-viewer】开箱即用,无需开发

sunsetglow/vue-pdf-viewer 开箱即用的pdf插件sunsetglow/vue-pdf-viewer, vue3 版本 无需多余开发,操作简单,支持大文件 pdf 滚动加载,缩放,左侧导航,下载,页码,打印,文本复制&…

Css—实现3D导航栏

一、背景 最近在其他的网页中看到了一个很有趣的3d效果,这个效果就是使用css3中的3D转换实现的,所以今天的内容就是3D的导航栏效果。那么话不多说,直接开始主要内容的讲解。 二、效果展示 三、思路解析 1、首先我们需要将这个导航使用一个大…

重新定义社媒引流:AI社媒引流王如何为品牌赋能?

在社交媒体高度竞争的时代,引流已经不再是单纯追求流量的数字游戏,而是要找到“对的用户”,并与他们建立真实的连接。AI社媒引流王通过技术创新和智能策略,重新定义了社媒引流的方式,帮助品牌在精准触达和高效互动中脱…

Docker1:认识docker、在Linux中安装docker

欢迎来到“雪碧聊技术”CSDN博客! 在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将…

Centos 8, add repo

Centos repo前言 Centos 8更换在线阿里云创建一键更换repo 自动化脚本 华为Centos 源 , 阿里云Centos 源 华为epel 源 , 阿里云epel 源vim /centos8_repo.sh #!/bin/bash # -*- coding: utf-8 -*- # Author: make.han

《硬件架构的艺术》笔记(五):低功耗设计

介绍 能量以热量形式消耗,温度升高芯片失效率也会增加,增加散热片或风扇会增加整体重量和成本,在SoC级别对功耗进行控制就可以减少甚至可能消除掉这些开支,产品也更小更便宜更可靠。本章描述了减少动态功耗和静态功耗的各种技术。…

Matlab 深度学习工具箱 案例学习与测试————求二阶微分方程

clc clear% 定义输入变量 x linspace(0,2,10000);% 定义网络的层参数 inputSize 1; layers [featureInputLayer(inputSize,Normalization"none")fullyConnectedLayer(10)sigmoidLayerfullyConnectedLayer(1)sigmoidLayer]; % 创建网络 net dlnetwork(layers);% 训…

LM2904运算放大器的应用:测电池电压

在电子设备的广泛应用中,电池作为便携设备的能量来源,其电压监测显得尤为关键。LM2904作为一款低功耗、高增益带宽积和高共模抑制比的双运算放大器,非常适用于电池电压的测量与监测。本文详细介绍了LM2904在电池电压测量方面的应用&#xff0…