[多线程]线程安全问题再讨论 - volatile

目录

1.引言

2.volatil关键字

2.1内存可见性

2.2指令重排序


1.引言

  大家好,我是老cu,今天我们来继续聊聊线程安全问题 线程安全是我们在编程开发中遇到的非常常见,棘手 的问题.同时也是多线程部分很复杂的问题.为了线程安全我们要做很多努力.也要对线程安全部分的代码进行慎重的写,本篇文章,我们将继续围绕线程安全问题来展开.

2.volatil关键字

2.1内存可见性

我们先看下面的代码:

import java.util.Scanner;

public class Test {
    public static int a = 0;
    public static void main(String[] args) {

        Thread t1 = new Thread(()->{
            while (a==0){
                
            }
        });
        Thread t2 = new Thread(()->{
            Scanner scanner = new Scanner(System.in);
            int b = scanner.nextInt();
            a=b;
        });
        t1.start();
        t2.start();
    }
}

很明显,上述代码是定义了一个全局静态变量a,并初始化为0,在t1线程里,通过while循环,如果a==0,则一直循环下去,在t2线程里改变a的值,理论上来说,如果我们输入一个非0的数,循环会停止.但是实际情况呢?

在我们运行以后发现,即使我们输入了一个数字循环还是没有结束.这是为什么呢?

  原来这是因为编译器为了执行效率,会进行优化,它检测到这个数字一直没有变化.本来的while循环步骤应该是直接在内存中读取a的值,然后在判断是否进行打印.但是为了效率,它优化的时候,发现这个数字并没有什么变化.就省去了去内存中读取这个步骤.直接从寄存器(工作内存)中读取数值a,此时我们在内存中修改这个数字,自然就影响不了结果了.

那么我们应该怎么做,可以让编译器每次在读取的时候,从内存中读取,而不是优化到寄存器中呢?

很简单,在这个变量前加入volatile关键字即可.

可以看到,加了这个volatile关键字以后,程序就能正常读取我们写入的数值了.

这就是volatile关键字的其中一个作用:解决内存可见性问题.

2.2指令重排序

为了举这个例子,我们先介绍一种设计模式-单例模式中的懒汉模式.单例模式是一种设计模式,它通过技巧,让一个类只能有一个对象,如果在实例化这个类,企图创建一个新的的对象就会报错.

当线程执行到new SingletonLazy();的时候

一般来说.new一个实例分为以下三步:
1.申请一块内存

2.实例化对象

3.将内存引用赋值

但是编译器有时候会进行指令重排序,上述的过程可能会变成 1 3 2 

在一个线程的时候,不会有什么问题.但是如果是两个线程的话

就会出现上述 的问题,为了解决它.我们可以将这个引用加入volatile关键字来禁止指令重排序:

class SingletonLazy{
    // 这个引用指向唯一实例. 这个引用先初始化为 null, 而不是立即创建实例
    private volatile static SingletonLazy singletonLazy = null;
    private static Object object = new Object();

    public static SingletonLazy getSingletonLazy() {
        if(singletonLazy == null){
            synchronized (object){
                if(singletonLazy == null){
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }
    private SingletonLazy(){

    }
}

这样就不会再出现问题了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络的分类

目录 一、按照传输介质进行分类 1、有线网络 2、无线网络 二、按照使用者进行分类 1、公用网 (public network) 2、专用网(private network) 三、按照网络规模和作用范围进行分类 1、PAN 个人局域网 2、LAN 局域网 3、MAN 城域网 4、 WAN 广域网 5、Internet 因特…

【算法】直接插入排序

目录 1. 说明2. 举个例子3. java代码示例4. java示例截图 1. 说明 1.直接插入排序的方式和打牌一样,刚开始数组为空 2.拿到一个数字后从左到右将它与数组中的每一个数字进行比较,然后插入合适的位置 3.到最后,数组按照既定的顺序排序好 2. 举…

代码随想录算法训练营第五十三天【动态规划part14】 | 1143.最长公共子序列、1035.不相交的线、53. 最大子序和

1143.最长公共子序列 题目链接 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 求解思路 动规五部曲 1.确定dp数组及其下标含义: dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序…

Tensorflow的日志log记录

if OUTPUT_GRAPH:tf.summary.FileWriter("logs/", sess.graph)自动创建文件夹log

GEE:Sobel算子卷积和Roberts算子卷积对比

作者:CSDN @ _养乐多_ 本文介绍了Sobel算子卷积和Roberts算子卷积操作的代码,并进行了图像对比,可以观察到两个算子的细微差异。 文章目录 一、Sobel算子和Roberts算子对比二、完整代码三、代码链接一、Sobel算子和Roberts算子对比 详细介绍介绍参考《遥感数字图像处理教程…

基于搜索协议实现工业设备升级

目录 1、背景引入 2、技术分析 3、过程概述 4、服务器端流程 5、客户端流程 6、效果展示 7、源码 7.1 master(主控) 7.2 device(设备) 8、注意事项 1、背景引入 在工业生产中,设备的升级和维护是非常重要的…

Gossip 协议

Gossip 协议 背景 在分布式系统中,不同的节点进行数据/信息共享是一个基本的需求。 一种比较简单粗暴的方法就是 集中式发散消息,简单来说就是一个主节点同时共享最新信息给其他所有节点,比较适合中心化系统。这种方法的缺陷也很明显&…

GOLAND搭建GIN框架以及基础框架搭建

创建GO环境文件夹 终端输入安装GIN go get -u github.com/gin-gonic/gin如果遇到超时错误 package golang.org/x/net/html: unrecognized import path "golang.org/x/net/html": https fetch: Get "https://golang.org/x/net/html?go-get1": dial tcp …

理解宏任务和微任务:JavaScript 异步编程的必备知识(上)

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

借助SD地图的BEV静态感知

动机与出发点 纯视觉、视觉Lidar的感知系统在复杂城市道路场景下并不能如预期那般表现稳定,其中遮挡就是一个巨大挑战。现在的BEV静态感知方案多采用多趟重建的方式获取,这就导致无论前方是否有车辆、建筑物、绿化带等,只要能投影到BEV空间的…

1688买家API接口跨境卖家需要的API接口

1688作为深耕产业带多年的数字供应链平台,近两年不仅在年轻消费群体中热度飙升,在跨境侧也有不俗表现。 11月19日,1688总裁余涌在1688跨境寻源通计划发布会上透露,1688平台拥有100万的源头厂商,每年服务6500万的B类买…

【JavaEE】多线程(3) -- 线程等待 wait 和 notify

目录 1. wait()⽅法 2. notify()⽅法 3. notifyAll()⽅法 4. wait 和 sleep 的对⽐(⾯试题) 由于线程之间是抢占式执⾏的, 因此线程之间执⾏的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执⾏先后顺序. 完成这个协调⼯…

树莓派4B机器狗的串口通信驱动库pyserial实践

pyserial是一个串口通信驱动库,常用的Windows、Linux、MacOS等都可以安装,这里我使用的是树莓派4B来测试,这块板子还是很强大的,我们可以通过pyserial这个库来操作基于这块板子上的机器狗之类的设备。 1、四足机器狗 本人的是四…

初识Java 18-6 泛型

目录 潜在类型机制 支持潜在类型机制的语言 Python的潜在类型机制 C的潜在类型机制 Java中的直接潜在类型机制 潜在类型机制的替代方案 反射 将方法应用于序列中的每个元素 Java 8的潜在类型机制(间接实现) 潜在类型机制的使用例(S…

条款2:不要滥用宏

文章目录 优先选择编译器而不是预编译器两种特殊情况使用宏替代函数调用总结 优先选择编译器而不是预编译器 假设我们预定义了一个宏#define ASPECT_RATIO 1.653,当我们的程序在这个地方出现错误的时候。可能会出现以下的问题: 符号名称ASPECT_RATIO可能…

MQTT客户端、代理(broker)和连接建立

在前篇文章(http://t.csdnimg.cn/IamPz)中,介绍了发布/订阅架构和MQTT如何据此交换信息,其中的关键概念是: 发布/订阅架构触耦了负责发布信息的客户端(发布者)和负责接收信息的客户端&#xff…

C语言-联合和枚举

------------------------------------ --------------- ------ 最慢的步伐不是跬步,而是徘徊; 最快的脚步不是冲刺,而是坚持。 今天来到我们的联合和枚举类型的讲解: 目录 联合体类型 联合体类型的声明 联合体类型的特点 …

Wireshark抓包分析RTMP协议时,出现Unknown问题

进行rtmp推流时,使用wireshark抓包,发现部分包显示Unknown 解决方法: 编辑 -> 首选项 -> Protocols -> RTMPT,这里Maximum packet size默认是32768 将该值调大,比如调成1048576,即可解决该问题。…

ChatGPT 的 18 种玩法,你还不会用吗?

你确定,你会使用 ChatGPT 了吗? 今天给大家整理了 18 种 ChatGPT 的用法,看看有哪些方法是你能得上的。 用之前我们可以打开R5Ai平台,可以免费使用目前所有的大模型 地址:R5Ai.com 语法更正 用途:文章…

改进LiteOS中物理内存分配算法(详细实验步骤+相关源码解读)

一、实验要求 优化TLSF算法,将Best-fit策略优化为Good-fit策略,进一步降低时间复杂度至O(1)。 优化思路: 1.初始化时预先为每个索引中的内存块挂上若干空闲块,在实际分配时避免分割(split)操作&#xff…