线程安全问题(一)——锁的简单使用

多线程安全问题

  • 线程安全问题的引入
  • 案例引入
    • 多线程指令排序问题
  • 线程不安全的原因
  • 解决线程不安全的方法
  • 锁的引入
    • 上锁和解锁过程
    • 一个简单的锁Demo
    • 对这个案例进行几次修改
  • 总结

线程安全问题的引入

在前面的博文中,我们了解到通过Thread.join()的方法让线程进入等待,能够在一定程度上解决线程抢占式执行的问题。回忆点这里
那么由于多线程代码而导致的bug,这样的问题就是线程安全问题。

案例引入

在下面的代码中,我希望执行之后得到count=100000的结果。

private static long count;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:"+count);}

结果如图:通过结果,我们每次运行得到的答案都是不一样的。
在这里插入图片描述

多线程指令排序问题

在上面的demo中,执行count++的指令并不只有一条这么简单。它可以分为三个步骤:(1) load操作: 读取内存中count的数值,保存到cpu寄存器中。 (2) add操作: 将寄存器中的count+1 (3) save操作:将寄存器中count的数值存放回内存中。
看似三条指令的简单操作,在多线程并发执行中却容易导致许多问题。
在多线程随即调度的执行状况下,两个线程的指令执行相对顺序可能就会存在多种可能,下面我列出几种可能性。
在这里插入图片描述
在上面的顺序中,我们可以理解为 在第一次t1线程的count++之后,count=1的值存放于寄存器中,接下来t2线程count++的时候,load指令下读取的数值为count=0,之后save操作后count=1,最后执行t1线程的save后值不变。因此执行了两次count++操作后,count的值只加了一次,出现了覆盖现象
我们可以不断扩大到其他状况,如t1线程辛辛苦苦加了100次,但是t2线程最终存放count=1将值覆盖了。

线程不安全的原因

  • 线程在系统中是随即调度,抢占式执行的。
  • 多个线程同时修改同一个变量(参考上述例子)
  • 线程对变量进行修改操作,非原子指令
  • 内存可见性问题
  • 指令重排序问题

解决线程不安全的方法

对于原因2,我们可以通过join等方式防止这种情况的发生,但这样做并不普适,属于少有的情况。

锁的引入

对于案例中的count++操作,我们清楚它不是一个原子操作,因此,程序猿想出来了一个办法:将上述的一系列“非原子”操作打包成一个“原子”操作。这样就能够避免线程不安全的问题。
基于这样的背景,锁被创建出来了。

上锁和解锁过程

假设存在两个线程t1和t2,
(1)上锁:我们首先给t1加上锁(lock),t2也尝试加同一把锁,那么这时候t2线程就会阻塞等待,在Java中该线程处于Blocked状态。
(2)解锁:当线程t1执行完锁住的部分后,线程t1解锁,接着由线程t2通过锁竞争拿到该锁(lock),加锁成功,t2线程转变为Runnable状态。
通过锁的存在,使得线程之间存在互斥的关系。在两个线程之间尚且都要通过锁竞争,而存在多个线程的情况下自然也要通过竞争的方式占据锁。这里必须要明确一个条件:线程之间竞争的必须要是同一把锁

一个简单的锁Demo

通过下面的代码块进行简单的解释。

  • 首先new一个Object类的对象object1.我们要把这个对象作为锁。看到这里我们就可以清楚,锁不必是某种特定的类,他只是一个标识,只是一个对象即可。
  • 在这段案例中,存在main、t1、t2三个线程,main线程十分简单,通过Thread.join()的方法等待t1和t2两个线程运行结束之后打印出结果即可。
  • 在线程t1和t2中,在for循环中,我们可以看到synchronized (object1)。其中synchronized就是锁的关键字。在t1和t2都使用了这段语句,即在进行count++操作之前,需要进行锁竞争,只有拥有锁的一方才可以进行count++操作。
  • 在这段代码块中,t1和t2在for循环的时候是并行执行的,而在锁竞争的时候是串行执行的。这样计算下来比单线程所花费的时间要少许多。
private static int count;
public static void main(String[] args) throws InterruptedException {Object object1 = new Object();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (object1) {count++;}}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (object1) {count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+count);}

运行结果如下图:
在这里插入图片描述

对这个案例进行几次修改

(1)接下来我们可以考虑,当锁对象为两个:object1和object2时,两个线程分别竞争两个不一样的锁,会出现什么情况
在这里插入图片描述
结果如下:
在这里插入图片描述
通过这个改变,我们可以理解不同的锁对象之间不存在互斥关系,因此二者之间也就不会发生锁竞争。

(2)将synchronized放在for循环外面的情况
在这种条件下,意味着当t1或t2某一个线程拿到这把锁之后,只有等循环结束以后才能释放了,很明显这样的情况所花费的资源甚至多于单线程。

Thread t1 = new Thread(()->{synchronized (object1) {for (int i = 0; i < 50000; i++) {count++;}}});Thread t2 = new Thread(()->{synchronized (object1) {for (int i = 0; i < 50000; i++) {count++;}}});

(3)在下面的代码中,设计Counter类进行add和get操作,在上面的代码中,我们已经知道锁对象只是一个标识,不关心它是怎样的存在,因此在这里,我们大胆的把counter对象作为锁对象。

class Counter {public int count;public  void add() {count++;}public int getCount() {return count;}
}
public class Demo3 {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (counter) {counter.add();}}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (counter) {counter.add();}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+counter.getCount());}
}

运行结果如下图:
在这里插入图片描述
(4)如果我们把锁加到add()方法中,我们通过this来指代对应的对象,这样做的情况是当多个线程调用该方法的时候,如果使用的是同一个对象会进行竞争,如果是不同对象的话则不会进行竞争。
同含义的写法为:synchronized public void add()

class Counter {public int count;public void add() {synchronizedthis){count++;}}public int getCount() {return count;}
}Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.add();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.add();}});

(5)对静态方法加锁
与(4)不同的是,加锁的对象为Counter这个类对象,因此如果多个线程调用func方法,则这些线程之间都会进行锁竞争

//第一种写法
public static void func(){synchronized (Counter.class) {//func}
}
//第二种写法
synchronized public static void func(){//func
}

总结

对于锁的概念需要逐渐深入,在本文中讲解了锁引入的原因以及锁的几种写法。
本文中使用的源码请戳此处

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

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

相关文章

统信UOS系统忘记登录密码怎么办

在使用统信操作系统UOS的时候有可能会出现忘记密码的情况&#xff0c;当遇到了用户登录密码忘记时如何修改&#xff1f;今天分享一下忘记超级系统管理员Root以及普通密码时的解决方法。 因为UOS系统版本的原因&#xff0c;UOS 1031操作系统取消了单用户更改密码的方法&#xff…

jupyter notebook的markdown语法不起作用

在这个界面编辑&#xff0c;发现markdown你编辑的是什么就是什么&#xff0c;不起作用&#xff0c;然而点一下&#xff1a; 右上角“Notebook转发”&#xff0c;就会单独跳出一个jupyter notebook的界面&#xff0c;此时就会奏效&#xff1a;

【CT】LeetCode手撕—72. 编辑距离

目录 题目1- 思路动规五部曲 2- 实现⭐72. 编辑距离——题解思路 3- ACM 实现 题目 原题连接&#xff1a;72. 编辑距离 1- 思路 模式识别&#xff1a;编辑举例 ——> 动态规划 动规五部曲 1.dp数组的含义 int[][] dp new int[word1.length()][word2.length()];以 i-1 …

6-47选择整数计算

整数计算&#xff1a; 用swing组件来实现整数计算&#xff0c;需要对整数计算的值进行校验。 import javax.swing.*; import java.awt.*; import java.awt.event.*;public class IntegerCalculator extends JFrame implements ActionListener {private JCheckBox[] checkBoxe…

【分布式文件系统HDFS】文件操作基本命令的使用

目录 一、按照下述要求写出相应的文件操作命令&#xff0c;执行并观察结果 1. 新建目录 1.1 在本地文件系统按要求创建如下的文件夹 1.2 在HDFS文件系统按要求创建如下的文件夹 2. 编辑文件test1.txt&#xff0c;放入本地文件夹 /opt/user/myfile 3. 使用moveFromLocal命令…

本地部署大模型的简单方法

https://ollama.com/https://ollama.com/ 在本地安装ollama windows版本。安装好后&#xff0c;就可以验证大模型了。 可以先测试一下qwen 0.5b&#xff0c;打开cmd&#xff0c;执行ollama run qwen:0.5b&#xff0c;首次会下载大模型的模型资源。 模型下好后就可以交互…

H5、Vue3、UniApp实现抖音短视频功能

H5、Vue3、UniApp实现抖音短视频功能 ml-swiper https://ext.dcloud.net.cn/plugin?id18973 可 0 配置&#xff0c;高性能、低代码、全端兼容 APP端效果图 微信小程序端效果图 Vue网页端效果图 ml-swiper 可 0 配置&#xff0c;高性能、低代码、全端兼容 APP端效果图 …

扩散模型 GLIDE:35 亿参数的情况下优于 120 亿参数的 DALL-E 模型

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…

媒体邀约中媒体采访应该如何做?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体宣传加速季&#xff0c;100万补贴享不停&#xff0c;一手媒体资源&#xff0c;全国100城线下落地执行。详情请联系胡老师。 在媒体邀约中&#xff0c;媒体采访应该遵循以下几个步骤和…

Python读取中文路径,出现乱码问题解决方案

Python读取中文路径&#xff0c;出现乱码问题解决方案 一、问题描述二、问题解决 欢迎学习交流&#xff01; 邮箱&#xff1a; z…1…6.com 网站&#xff1a; https://zephyrhours.github.io/ 一、问题描述 笔者在使用opencv读取带有中文路径的图片时&#xff0c;发现会出现乱…

C++ 模板:全特化和偏特化

目录 全特化&#xff08;Full Specialization&#xff09; 偏特化&#xff08;Partial Specialization&#xff09; 特点和使用场景 注意事项 在C中&#xff0c;模板特化&#xff08;template specialization&#xff09;是一种强大的功能&#xff0c;允许对模板进行特定情…

2024最新算法:鳗鱼和石斑鱼优化(Eel and grouper optimizer,EGO)算法求解23个函数,MATLAB代码

一、算法介绍 鳗鱼和石斑鱼优化器&#xff08;Eel and grouper optimizer&#xff0c;EGO&#xff09;是2024年提出的一种智能优化算法&#xff0c;EGO算法的灵感来自海洋生态系统中鳗鱼和石斑鱼的共生相互作用和觅食策略。 参考文献&#xff1a; [1]A. Mohammadzadeh, S. Mi…

玩转数据库索引

1、概述 通常我们要对数据库进行优化&#xff0c;主要可以通过以下五种方法。 计算机硬件调优应用程序调优数据库索引优化SQL语句优化事务处理调优 本篇文章将向大家介绍数据库中索引类型和使用场合&#xff0c;本文以SQL Server为例&#xff0c;对于其他技术平台的朋友也是有…

DDL-表操作-数据类型

一.DDL-表操作-数据类型 MySQL中的数据类型有很多,主要分为三类:数值类型,字符串类型,日期类型。 二.关系表 注意: 无符号和有符号的取值范围不是一样的,无符号需要加上UNSIGNED范围。 BLOB&#xff1a;用来描述二进制数据 TEXT:用来描述字符串 三.定长字符串和变长字符串 c…

【STM32入门学习】学习嵌入式实时操作系统(RTOS)移植uc/OS到stm32F103上

目录 一、建立STM32HAL库工程 1.1实时操作系统 1.2基于HAL库创建工程 二、获取uC/OS-III源码 三、移植准备 3.1复制uC/OS-III文件到工程文件夹 3.2添加工程组件和头文件路径 四、移植修改代码 &#xff14;.1.启动文件修改&#xff1a; &#xff14;.2.app_cfg.h &a…

Java Scanner 类

Java Scanner 类 java.util.Scanner 是 Java5 的新特征&#xff0c;我们可以通过 Scanner 类来获取用户的输入。 下面是创建 Scanner 对象的基本语法&#xff1a; Scanner s new Scanner(System.in);接下来我们演示一个最简单的数据输入&#xff0c;并通过 Scanner 类的 nex…

关于FPGA对 DDR4 (MT40A256M16)的读写控制 4

关于FPGA对 DDR4 &#xff08;MT40A256M16&#xff09;的读写控制 4 语言 &#xff1a;Verilg HDL 、VHDL EDA工具&#xff1a;ISE、Vivado、Quartus II 关于FPGA对 DDR4 &#xff08;MT40A256M16&#xff09;的读写控制 4一、引言二、DDR4 SDRAM设备中模式寄存器重要的模式寄存…

数组中的逆序对

描述&#xff1a; https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/description/ 思路&#xff1a; 在进行归并排序时&#xff0c;会分成有序的左右两部分&#xff0c;如果左部分出现了大于右部分的数时&#xff08;nums[cur1] > nums[cur2]&#xff09;&…

Javaweb配置tomcat

Tomcat 9版本链接 链接&#xff1a;https://pan.baidu.com/s/1u-eDur5KlqlXM_IM50Ahtg?pwd1njm 提取码&#xff1a;1njm 1、打开idea&#xff0c;创建maven项目 2023版IDEA 2、 目录结构 ps: 如果结果不完整,选中main右键 新建对应的文件夹 3、 web项目设置Tomcat(部署项目…

特征工程与数据预处理全解析:基础技术和代码示例

在机器学习和数据科学的世界里&#xff0c;数据的质量是建模成功与否的关键所在。这就是特征工程和数据预处理发挥作用的地方。本文总结的这些关键步骤可以显著提高模型的性能&#xff0c;获得更准确的预测&#xff0c;我们将深入研究处理异常值、缺失值、编码、特征缩放和特征…