线程进阶(以解决线程安全问题为主)、volatile的底层实现

线程:以解决线程安全问题为主

进程:运行时程序,操作系统分配内存资源的最小单位。

线程 :进程内部最小执行单元。

多线程的优点:提高程序响应速度,可以多个线程各自完成自己的工作,提高设备利用率。

缺点:在多个线程同时访问共享数据,可能会出现资源共享问题。

并发执行:在一个时间段内对多个线程依次执行

并行执行:是真正意义上同时执行,两个线程在同一时间节点上一起执行

并发编程的核心问题:

1,不可见性:一个线程对共享变量修改,另一个线程不能立刻看到,称不可见性。(缓存不能及时刷新导致)
public class Demo {public static void main(String[] args) {RunTask runTask=new RunTask();//创建任务对象Thread thread=new Thread(runTask);//创建线程thread.start();//启动线程while(true){if(!runTask.isFlag()) {System.out.println("main:"+runTask.isFlag());break;}}}
}
public class RunTask implements Runnable{private boolean flag=false;@Overridepublic void run() {flag=true;//设置此时flag为trueSystem.out.println(flag);}public  boolean isFlag() {return flag;}public  void setFlag(boolean flag) {this.flag = flag;}
}

 

先启动了一个线程任务,设置的flag为true。但是在main线程取出flag为false。发现读取的数据不一致,说明了线程的不可见性。我们该如何避免这种,在一个线程修改后其他线程可以立刻看见修改后的数据呢?----------添加关键字volatile


public class RunTask implements Runnable{private volatile boolean flag=false;@Overridepublic void run() {flag=true;//设置此时flag为trueSystem.out.println(flag);}public  boolean isFlag() {return flag;}public  void setFlag(boolean flag) {this.flag = flag;}
}

为要修改的变量添加修饰volatile发现可以使改变的变量立即被看见。

 volatile修饰的变量在一个线程中被修改后,对其他线程立即可见。并且禁止指令重排。

volatile的底层实现

 该关键字可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为 volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。volatile 的内存语义和synchronized有相似之处,具体来说就是,当线程写入了 volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取 volatile 变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。

 volatile 虽然提供了可见性保证,但并不保证操作的原子性。


那么一般在什么时候才使用 volatile关键字呢?
①写入变量值不依赖变量的当前值时。因为如果依赖当前值,将是获取一计算一写入三步操作,这三步操作不是原子性的,而 volatile 不保证原子性。
②读写变量值时没有加锁。因为加锁本身已经保证了内存可见性,这时候不需要把变量声明为volatile的。

2,乱序性:指令在执行过程中改变顺序,可能会影响程序运行结果

为了优化性能,有时候会改变程序中语句的先后顺序。

public class Test {static int a = 0, b = 0, x = 0, y = 0;public static void main(String[] args) {while (true) {x=0;y=0;a=0;b=0;Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {a = 1;x = b;}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {b = 1;y = a;}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}if (x == 0 && y == 0) {System.err.println(x + "---" + y);break;}else{System.out.println(x+" "+y);}}}
}

每次执行每个线程的count值都不同。由于三个线程是并发执行,所以不确定他们的执行顺序,这就是并发的乱序问题。

解决办法:

1,上锁

    public void increment() {synchronized (obj) {count++; // 这是一个原子操作,不可中断System.out.println("Thread " + Thread.currentThread().getId() + " incremented count to " + count);}}

2, volatile

3,非原子性:线程切换带来的非原子性问题

A线程执行时,被切换到B线程。

使用Javap-c命令查看汇编代码,如下所示。
        public void inc();
        Code:
                0:aload0
                1:dup
                2:getfield
                5:Iconst 1
                6:ladd
                7:putfield10:return
由此可见,简单的++value 由2、5、6、7四步组成,其中第2步是获取当前 value的值并放入栈顶,第5步把常量1放入栈顶,第6步把当前栈顶中两个值相加并把结果放入栈顶,第7步则把栈顶的结果赋给 value变量。因此,Java中简单的一句+value 被转换为汇编后就不具有原子性了。 

解决方案:

1,使用synchronized关键字

2,CAS机制:不加锁的机制

public class Demo {private volatile  int count = 0;public static void main(String[] args) {Demo demo = new Demo();// 创建并启动三个线程,它们都会访问demo对象的increment()方法Thread t1 = new Thread(() -> {demo.increment();});Thread t2 = new Thread(() -> {demo.increment();});Thread t3 = new Thread(() -> {demo.increment();});t1.start();t2.start();t3.start();try {t1.join();t2.join();t3.join();} catch (InterruptedException e) {e.printStackTrace();}// 输出最终的count值,由于并发中的乱序问题,这个值可能会与预期不符System.out.println("Final count: " + demo.count);}public void increment() {System.out.println("Thread " + Thread.currentThread().getId() + " incremented count to " + ++count);}
}

 

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

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

相关文章

原生SSM整合(Spring+SpringMVC+MyBatis)案例

SSM框架是Spring、Spring MVC和MyBatis三个开源框架的整合,常用于构建数据源较简单的web项目。该框架是Java EE企业级开发的主流技术,也是每一个java开发者必备的技能。下面通过查询书籍列表的案例演示SSM整合的过程. 新建项目 创建文件目录 完整文件结…

docker安装运行CloudBeaver并设置默认语言为中文

1、CloudBeaver CloudBeaver 是一个开源的 Web 数据库管理工具,它提供了一个基于浏览器的用户界面,允许用户管理和操作各种类型的数据库。CloudBeaver 支持多种数据库系统,包括但不限于 PostgreSQL、MySQL、SQLite、Oracle、SQL Server 以及…

微调您的Embedding模型以最大限度地提高RAG管道中的相关性检索

英文原文地址:https://betterprogramming.pub/fine-tuning-your-embedding-model-to-maximize-relevance-retrieval-in-rag-pipeline-2ea3fa231149 微调您的Embedding模型以最大限度地提高RAG管道中的相关性检索 微调嵌入前后的 NVIDIA SEC 10-K 文件分析 2023 年…

一篇综述洞悉医学大型语言模型的原理,应用和挑战

在过去的一年中,随着 GPT-4、LLaMA、Mistral,PaLM 等先进技术的突飞猛进,大型语言模型(Large Language Models)已经引领全球人工智能进入了一个全新的基础模型时代,这一时代不仅开启了技术创新的新篇章&…

QT图表-折线图、曲线图

时间记录:2024/1/15 一、使用步骤 1.添加图表模块 .pro项目管理文件中添加charts模块 QChart类:图表类 QChartView类:图表显示类 2.ui文件中添加QChartView组件 (1)选择一个QGrapicsView组件将其拖拽到ui界面上合适位…

前端背景收集之烟花背景

文章目录 🐒个人主页🏅Vue项目常用组件模板仓库📖前言:🎀源码如下: 🐒个人主页 🏅Vue项目常用组件模板仓库 📖前言: 本篇博客主要提供前端背景收集之烟花背景…

RabbitMQ入门精讲

1. 什么是消息队列 消息指的是两个应用间传递的数据。数据的类型有很多种形式,可能只包含文本字符串,也可能包含嵌入对象。 “消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。…

【STM32】STM32学习笔记-MPU6050简介(32)

00. 目录 文章目录 00. 目录01. MPU6050简介02. MPU6050参数03. MPU6050硬件电路04. MPU6050框图05. MPU6050常用寄存器06. 附录 01. MPU6050简介 •MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合&#xff0…

【SpringBoot系列】JDK动态代理

🤵‍♂️ 个人主页:@香菜的个人主页,加 ischongxin ,备注csdn ✍🏻作者简介:csdn 认证博客专家,游戏开发领域优质创作者,华为云享专家,2021年度华为云年度十佳博主 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收…

SpringMVC入门案例

引言 Spring MVC是一个基于MVC架构的Web框架,它的主要作用是帮助开发者构建Web应用程序。它提供了一个强大的模型驱动的开发方式,可以帮助开发者实现Web应用程序的各种功能,如请求处理、数据绑定、视图渲染、异常处理等。 开发步骤 1.创建we…

阿里云PAI部署GLM3,访问403

启动之后,直接访问,报错 另辟蹊径: 修改web_demo_gradio.python o.launch(server_name“127.0.0.1”, server_port8501, inbrowserTrue, shareTrue),分享出去 gradio 无法生成公共链接,解决办法: 下载…

数字创意市场:Web3时代创作者的新机遇

随着Web3时代的崭露头角,数字创意市场正迎来全新的变革和机遇。在这个数字化的时代,创作者们将面对更加开放、去中心化的创作和交易环境。本文将深入探讨Web3时代数字创意市场为创作者带来的新机遇,以及这个时代为创意产业带来的变革。 创作者…