深入理解线程安全

news2024/11/20 18:46:36

引言:

在多线程编程中,线程安全是一个至关重要的概念。线程安全可能到导致数据不一致,应用程序崩溃和其他不可预测的后果。本文将深入探讨线程安全问题的根本原因,并通过Java代码示例演示如何解决这些问题。


线程安全的根本原因

这里先观察一个线程不安全的例子:

public class Test {
    private static int count;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                count++;
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                count++;
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();


        System.out.println(count);

    }
}

这里我们想要得到 count 的值为200,但是运行的结果却不是。

这里的 count++ 实际操作分为3步:

1. load把数据从内存,读到 cpu 寄存器中(可能先t1,也可能先t2)。

2. add 将寄存器中的数据进行 +1。

3. save 把寄存器中的数据,保存到内存中 。

因为线程的调度是随机的,所以在 count++ 时会出现下面等很多不同的结果

针对以上我们可以知道产生线程安全的原因:

1. 操作系统中,线程的调度是随机的

2. 两个线程针对同一个变量进行修改

3. 修改操作,不是原子性的

    此处count++是 非原子 的操作(先读,再修改)

4. 内存可见性问题

解决线程安全

使用synchronized关键字可以将代码块或方法标记为同步的,这样只有一个线程可以访问它。这可以防止多个线程同时访问共享数据。

public class Test {
    private static int count;
    private static Object locker = new Object();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                for (int i = 0; i < 100; i++) {
                    count++;
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (locker) {
                for (int i = 0; i < 100; i++) {
                    count++;
                }
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();


        System.out.println(count);

    }
}

这串代码,对其进行 synchroniezd 加锁后,答案就能是200了

synchronized 的特性

1. 互斥性

2.可重入性

互斥性

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到 同一个对象 synchronized 就会阻塞等待。

进入 synchronized 修饰的代码块, 相当于 加锁

退出 synchronized 修饰的代码块, 相当于 解锁

可重入性

在下面的代码中,increase 和 increase2 两个方法都加了 synchronized, 此处的 synchronized 都是针对 this 当前 对象加锁的. 在调用 increase2 的时候, 先加了一次锁, 执行到 increase 的时候, 又加了一次锁. (上个锁还没释 放, 相当于连续加两次锁)

class Counter {
    public int count = 0;
    synchronized void increase() {
        count++;
    }
    synchronized void increase2() {
        increase();
    }
}

死锁

哲学家就餐问题:

假设有几个哲学家,围着一个桌子(上有一盘菜),每两个人之间有1个筷子,哲学家吃饭的的时间是随机的,吃饭的时候会拿起左右两边的筷子(先拿左边的,没拿起右手的不会放下左手的),正常情况下是可以吃的,但是如果同时拿起左边的筷子,就 " 卡 "到这了 。

死锁的原因

1. 互斥使用,当一个线程持有一把锁时,另一个也想要得到锁,就必须阻塞等待(锁的基本特性)

2. 不可抢占,当锁已经被线程1拿到后,线程2只能等待1主动释放(锁的基本特性)

3. 请求保持,一个线程尝试获取多把锁(代码结构:下面例子)

4. 循环等待(代码结构:哲学家就餐问题)

解决死锁

所以解决死锁1,和 2是锁的本质不能改变

我们只能调整:

对于3来说,改变代码结构

对于4来说,约定加锁的顺序

volatile关键字

1. 保存内存可见性

2. 不保证原子性

保存内存可见性

讨论内存可见性的话,就不得不谈论一下下面的知识点:

计算机cpu访问数据(存在内存中)的时候,会先读出来,放到寄存器中,再进行运算。cpu读内存的操作相对于其他操作来说是比较慢的。所以编译器为了提高效率,把本来要读内存的操作,优化成了读寄存器,减少了读内存的操作。eg:

public class Demo17 {
    private static volatile int isQuit = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (isQuit == 0) {
                // 循环体里啥都没干.
                // 此时意味着这个循环, 一秒钟就会执行很多很多次.
            }
            System.out.println("t1 退出!");
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            System.out.println("请输入 isQuit: ");
            Scanner scanner = new Scanner(System.in);
            // 一旦用户输入的值, 不为 0, 此时就会使 t1 线程执行结束.
            isQuit = scanner.nextInt();
        });
        t2.start();
    }
}

这里即使你输入的不是0,也会一直循环!!!

因此为解决这个问题,就可以在变量的前面加上volatile!!!

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

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

相关文章

element plus Infinite Scroll 无限滚动

欢迎关注我的公众号&#xff1a;夜说猫&#xff0c;让一个贫穷的程序员不靠打代码也能吃饭~ element plus官网中&#xff0c;Infinite Scroll示例使用的是数字&#xff0c;在实际项目运用中&#xff0c;我们更多的是使用json数组进行渲染&#xff0c;所以我们改写v-infinite-sc…

Visual Studio2019报错

1- Visual Studio2019报错 错误 MSB8036 找不到 Windows SDK 版本 10.0.19041.0的解决方法 小伙伴们在更新到Visual Studio2019后编译项目时可能遇到过这个错误&#xff1a;“ 错误 MSB8036 找不到 Windows SDK 版本 10.0.19041.0的解决方法”&#xff0c;但是我们明明安装了该…

网络安全攻防对抗之隐藏通信隧道技术整理

完成内网信息收集工作后&#xff0c;渗透测试人员需要判断流量是否出得去、进得来。隐藏通信隧道技术常用于在访问受限的网络环境中追踪数据流向和在非受信任的网络中实现安全的数据传输。 一、隐藏通信隧道基础知识 &#xff08;一&#xff09;隐藏通信隧道概述 一般的网络通…

Python图像融合处理和 ROI 区域绘制基础

文章目录 一、图像融合二、图像 ROI 区域定位三、图像属性3.1 shape3.2 size3.3 dtype四、图像通道分离及合并4.1、split()函数4.2 merge()函数五、图像类型转换一、图像融合 图像融合通常是指多张图像的信息进行融合,从而获得信息更丰富的结果,能够帮助人们观察或计算机处理…

微服务保护-隔离

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…

SOAP WebService 发布服务成功,但是访问404

原因 我这里是出在路由问题&#xff0c;因为一般我们都会配置WebServiceConfig&#xff0c;WebServiceConfig里又会定义ServletRegistrationBean&#xff0c;用于将一个Servlet注册到Web应用程序中&#xff0c;这里会配置上路径&#xff0c;如下&#xff1a; 但是项目有可能在…

再战SDRAM与资料整理。

总之只要阅读操作手册&#xff0c;按照时序来&#xff0c;完全不难&#xff01; 器件记录&#xff1a; 小梅哥AC620上SDRAM&#xff1a;M12L2561616A-6TG2T 其的存储空间为16M*16256MB&#xff0c;第二行的数字则与其速度等级有关&#xff1b;其分为&#xff1a; 4bank*16bit…

NLP(6)--Diffusion Model

目录 一、Flow-Based General Model 1、概述 2、函数映射关系 3、Coupling Layer 4、Glow 二、Diffusion Model 1、概述 2、前向过程 3、反向过程 4、训练获得噪声估计模型 5、生成图片 三、马尔科夫链 一、Flow-Based General Model 1、概述 Flow-Based General…

C 通过宏定义重定义malloc - free,预防内存泄露

系列文章目录 C模版基础 文章目录 目录 代码地址 相关说明 使用案例 代码地址 GitHub - CHENLitterWhite/CPPWheel: CPP自封装的库 /* * 作者: 干饭小白 * 时间: 2023-09-25 16:00:00:00 * * 说明: * 只能检测 malloc 和 free,无法检测 new delete */ #pra…

[Linux入门]---Linux指令②

文章目录 Linux系统常用指令1.man指令2.echo3.cp指令&#xff08;重要&#xff09;4.mv指令&#xff08;重要&#xff09;&#xff1a;5.alias指令6.cat指令7.more指令8.less指令&#xff08;重要&#xff09;9.head指令10.tail指令11.时间相关的指令1.在显示方面2.在设定时间方…

Redis环境配置

【Redis解压即可】链接&#xff1a;https://pan.baidu.com/s/1y4xVLF8-8PI8qrczbxde9w?pwd0122 提取码&#xff1a;0122 【Redis桌面工具】 链接&#xff1a;https://pan.baidu.com/s/1IlsUy9sMfh95dQPeeM_1Qg?pwd0122 提取码&#xff1a;0122 Redis安装步骤 1.先打开Redis…

OpenAI开发系列(二):大语言模型发展史及Transformer架构详解

全文共1.8w余字&#xff0c;预计阅读时间约60分钟 | 满满干货&#xff0c;建议收藏&#xff01; 一、介绍 在2020年秋季&#xff0c;GPT-3因其在社交媒体上病毒式的传播而引发了广泛关注。这款拥有超过1.75亿参数和每秒运行成本达到100万美元的大型语言模型&#xff08;Large …

【力扣周赛】第 362 场周赛(⭐差分匹配状态压缩DP矩阵快速幂优化DPKMP)

文章目录 竞赛链接Q1&#xff1a;2848. 与车相交的点解法1——排序后枚举解法2——差分数组⭐差分数组相关题目列表&#x1f4d5;1094. 拼车1109. 航班预订统计2381. 字母移位 II2406. 将区间分为最少组数解法1——排序贪心优先队列解法2——差分数组 2772. 使数组中的所有元素…

加密算法发展简介

1&#xff1a;对称加密算法 客户端加密数据和服务端解密数据&#xff0c;使用的相同的秘钥&#xff1a; 固定秘钥&#xff1a;双方约定好一个固定秘钥&#xff1b; 随机秘钥&#xff1a;双方约定每次建立连接的时候&#xff0c;某固定BYTE为秘钥&#xff1b; 缺点&#xff1a…

【MySQL】 MySQL数据库基础

文章目录 &#x1f431;‍&#x1f453;数据库的操作&#x1f4cc;显示当前的数据库&#x1f4cc;创建数据库&#x1f388;语法&#xff1a;&#x1f388;语法说明&#x1f388;示例&#xff1a; &#x1f334;使用数据库&#x1f38b;删除数据库&#x1f431;‍&#x1f3cd;语…

sql注入Less-2

后台sql s q l " S E L E C T ∗ F R O M u s e r s W H E R E i d sql "SELECT * FROM users WHERE id sql"SELECT∗FROMusersWHEREidid LIMIT 0,1"; 注入语句 http://192.168.200.26/Less-3/?id-1? union select 1,2,database();– 使用id-1 便可…

JavaScript中的Hoisting机制

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ JavaScript 中的 Hoisting 机制⭐ 变量提升⭐ 函数提升⭐ 注意事项⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xf…

LVS+Keepalived 高可用集群

LVSKeepalived 高可用集群 1、Keepalived工具介绍2、vrrp协议&#xff08;虚拟路由冗余协议&#xff09;2.1vrrrp是什么&#xff1f;2.2vrrp工作过程2.3Keeplived、VRRP及其工作原理2.4Keepalived体系主要模块 3、搭建LVSKeepalived 高可用集群 1、Keepalived工具介绍 支持故障…

MFC-GetSystemFirmwareTable获取系统固件表

获取ACPI表格 void CgetSystemFirmwareTableDlg::OnBnClickedButton1() {//UINT bufferSize GetSystemFirmwareTable(ACPI, 0, NULL, 0);//获取系统固件表/*【参数3和参数4为NULL和0&#xff0c;只是为了返回真实大小】这个函数可以用来获得系统中的各种固件信息&#xff0c;如…

MySQL中SQL语句执行顺序分析

1、 MySQL手写执行顺序 2、 MySQL机读执行顺序 随着Mysql版本的更新换代&#xff0c;其优化器也在不断的升级&#xff0c;优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。下面是经常出现的查询顺序&#xff1a;