多线程之旅:开启多线程安全之门的钥匙

上次,小编分享到了多线程出现了安全问题。

那么接下来小编来分享下是如何解决这个多线程安全问题的。

上次分享到,多线程安全问题的“罪魁祸首”:线程调度的不确定性。

就是说呢,A、B、C三个线程,然后,A线程执行完,下次系统分配资源执行的时候,不知道是B先执行,还是C先执行,具有不确定性。

就像是在一个房间内,有个领导听A、B、C汇报

A在里面说说话,话没说完呢,就被扯出去了,又随机放 一个人进去房间说话,所以最终导致领导听的汇报出现不一致。

所以,我们可以这样子,这个房间内我加一把锁,A进去之后,锁住房间,期间内不能让其他人进去,等A说完后,再把锁给其他人,按照刚刚的方法。

那么java呢,提供了这样的一个东西,可以保证线程安全。

synchronized

synchronized 是 Java 中的一个关键字,用于控制多线程对共享资源的访问,确保同一时间只有一个线程可以执行某个代码块或方法,从而避免线程间的竞争条件和数据不一致。

用法如下:

synchronized (括号中是一个对象){

}

{}内放的是要打包成一整个整体的代码。

此时呢,如若进入代码块,那么就会针对里面的代码进行加锁。

出了代码块就会解锁。

值得注意的是,锁是不可被抢占,一个线程拿到了锁,其他线程要想拿到这个锁,必须等待这个锁被释放。

使用上一篇文章,开头给的例子,它是线程不安全的例子,接下来使用synchronized。

代码实例:

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

显然,这个代码中,实例化一个Object对象,然后把这个对象名locker作为参数给到t1线程和t2线程中的synchronized。

可见我们的运行结果是正确的。

这里要提到的是,传入给synchronized的参数不一定要求是Object的,可以是其他。

比如String locker=new String()、public static int [] locker=new int[100];

值得注意的是,要对同一个对象加锁才行,对不同的对象加锁,所起的作用是无效的。

那么这个加锁操作只能这样子了吗?

当然不是,还有其他的操作。

比如

synchronized可以修饰方法。

代码实例

class Counter{public static int count=0;synchronized public void add(){count++;}
}
public class Demo14 {public static void main(String[] args) throws InterruptedException {//synchronized修饰方法Counter counter=new Counter();Thread t1=new Thread(()->{for (int i=0;i<5000;i++){synchronized (counter){counter.add();}}});Thread t2=new Thread(()->{for(int i=0;i<5000;i++){synchronized (counter){counter.add();}}});t1.start();t2.start();t1.join();t2.join();System.out.println(Counter.count);}
}

那么这个add()方法可以等价于这样写的

  public void add(){synchronized (this){count++;}}

里面的参数改为了this,即谁调用该方法,谁就要被锁了。

除了这样的操作,这个synchronized还可以修饰静态方法。

synchronized可以修饰静态方法。

代码实例:

class Counter{public static int count=0;synchronized public static void add(){count++;}
public class Demo14 {public static void main(String[] args) throws InterruptedException {//synchronized修饰方法Counter counter=new Counter();Thread t1=new Thread(()->{for (int i=0;i<5000;i++){synchronized (counter){Counter.add();}}});Thread t2=new Thread(()->{for(int i=0;i<5000;i++){synchronized (counter){Counter.add();}}});t1.start();t2.start();t1.join();t2.join();System.out.println(Counter.count);}
}

同样的add()方法等价于:

public static void add(){synchronized (Demo14.class){count++;}
}

那么里面的Demo14.class是什么意思呢?

Demo14.class用于获取Demo14的Class的对象。

每个主类都只有一个Class对象。

当然也可以这样写的

synchronized (Demo14.class){count++;
}

然而,锁这么好用,但是也不能随便用的,会发生死锁问题的。

死锁问题

那什么 又是死锁呢?

场景一:一个线程一把锁,然后这个线程对此把锁进行连续加锁两次。

来一个代码引入:

 public static Object locker=new Object();public static int count=0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i=0;i<5000;i++){synchronized (locker){count++;synchronized (locker){}}}});t1.start();
}

显然此时,synchronized使用了两次,还是用了同一个locker对象。

那么此时当代码进入第一个sychronized的代码块内部

那么这时候,里面就拿到锁了。

然后当代码执行到第二个scychronized的时候,此时发现,这个locker对象,还是被第一个sychronized持有,因为它还没出代码块呢,所以还没释放这个锁资源。

所以,第二个synchronized想拿到locker对象,但是第一个sychronized还没释放这个锁,

要释放这个锁,就必须把第二个sychronized加锁给执行完,显然,就僵住了,就是说阻塞了,你不让我,我不让你。

但是,看会执行结果,显然运行成功了。

这是为什么呢?

因为这是因为java的synchronized是一个可重入锁。

那什么又是可重入锁?

可重入锁(Reentrant Lock) 是一种同步机制,允许同一个线程多次获取同一把锁。这种锁是可重入的,意味着如果一个线程已经持有了某个锁,它可以再次获取该锁而不会被阻塞。

意思就是说,加锁的时候判定下,当前这个锁是否是被占用状态,是被哪个线程占用了,如若是当前线程对这个锁进行多次加锁,此时后续的加锁将不会进行真正的加锁操作。

所以,以上的例子情况是synchronized对这情况进行了特殊处理了。

场景二:两个线程,两把锁

即有两个线程,t1和t2,锁1,锁2

t1线程一开始对锁1进行加锁,t2线程一开始对锁2加锁

t1线程不释放锁1的情况下,再对锁2进行加锁,t2线程不释放锁2情况下,再对锁1进行加锁

代码引入:

public class Demo15 {public static Object locker1=new Object();public static Object locker2=new Object();public static void main(String[] args) {Thread t1=new Thread(()->{synchronized (locker1){System.out.println("t1加锁locker1完成!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("t1加锁locker2完成!");}}});Thread t2=new Thread(()->{synchronized (locker2){System.out.println("t2加锁locker2完成!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1){System.out.println("t2加锁locker1完成!");}}});t2.start();t1.start();}
}

运行结果

此时,出现了阻塞,还有两条语句没有进行打印。

可以看出,代码出现了循环依赖咯。

这也是个死锁发生的例子。

当然,上面是两把锁,两个线程。

如若是N个线程,M把锁,此时呢,一旦出现问题,效果也是类似的,就是会阻塞特别严重。

ok,接下来总结下,死锁发生的条件

死锁必要条件

1.锁是互斥的(基本特性)
2.锁是不可抢占的(基本特性)

3.请求和保持(代码结构)

即在拿到A锁情况下,同时不释放锁A,去拿锁B,但此时,锁B也没有释放资源

4.循环等待(代码结构),多个线程获取锁的过程中,出现了循环等待

那么出现了死锁,那就要尝试去解决它。

尝试解决死锁问题

我们可以从条件入手,当然条件1、2显然是不能用了,毕竟是基本特性。

条件3中,我们可以尝试这样解决

1.一次性申请所有资源,即不是在执行过程中,再次逐步申请

2.资源预分配

在任务启动之前,预先分配所有需要的资源。

……

条件4中

有一个简单的办法,即对锁进行排序,按照锁的顺序来,进行一定顺序的加锁,那么此时可以避免死锁问题。

比如,这个例子

代码实例:

public class Demo15 {public static Object locker1=new Object();public static Object locker2=new Object();public static void main(String[] args) {Thread t1=new Thread(()->{synchronized (locker1){System.out.println("t1加锁locker1完成!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("t1加锁locker2完成!");}}});Thread t2=new Thread(()->{synchronized (locker1){System.out.println("t2加锁locker2完成!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("t2加锁locker1完成!");}}});t2.start();t1.start();}
}

运行结果

此时呢,加锁顺序都是按照locker1再到locker2的顺序来,显然,运行是没有问题的。

当然,解决死锁问题,还有一个方案

即银行家算法

核心思想:在分配资源之前,系统会检查此次分配是否会导致系统进入不安全状态。如果分配后系统仍然是安全的,才允许分配;否则,拒绝分配。

但是由于日常开发中不是经常使用这个,毕竟不够接地气,所以这里不多介绍了。

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

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

相关文章

Docker 单机快速部署大数据各组件

文章目录 一、Spark1.1 NetWork 网络1.2 安装 Java81.3 安装 Python 环境1.4 Spark 安装部署 二、Kafka三、StarRocks四、Redis五、Rabbitmq六、Emqx6.1 前言6.2 安装部署 七、Flink八、Nacos九、Nginx 一、Spark 1.1 NetWork 网络 docker network lsdocker network create -…

设计模式的艺术-职责链模式

行为型模式的名称、定义、学习难度和使用频率如下表所示&#xff1a; 1.如何理解职责链模式 最常见的职责链是直线型&#xff0c;即沿着一条单向的链来传递请求。链上的每一个对象都是请求处理者&#xff0c;职责链模式可以将请求的处理者组织成一条链&#xff0c;并让请求沿着…

CSDN 博客之星 2024:默语的技术进阶与社区耕耘之旅

CSDN 博客之星 2024&#xff1a;默语的技术进阶与社区耕耘之旅 &#x1f31f; 默语&#xff0c;是一位在技术分享与社区建设中坚持深耕的博客作者。今年&#xff0c;我有幸再次入围成为 CSDN 博客之星TOP300 的一员&#xff0c;这既是对过往努力的肯定&#xff0c;也是对未来探…

Mysql的数据类型(学习自用)

一、整数型数据类型 名称大小&#xff08;字节数&#xff09;范围(无符号&#xff09;范围&#xff08;有符号&#xff09;TINYINT10-255-2^8~2^8-1SMALLINT20-65535-2^16~2^16-1MEDIUMINT30-2^24-1-2^24~2^24-1INT40-2^32-1-2^32~2^32-1BIGINT80-2^64-1-2^64~2^64-1 二、浮点…

深入 Flutter 和 Compose 的 PlatformView 实现对比,它们是如何接入平台控件

在上一篇《深入 Flutter 和 Compose 在 UI 渲染刷新时 Diff 实现对比》发布之后&#xff0c;收到了大佬的“催稿”&#xff0c;想了解下 Flutter 和 Compose 在 PlatformView 实现上的对比&#xff0c;恰好过去写过不少 Flutter 上对于 PlatformView 的实现&#xff0c;这次恰好…

winfrom项目,引用EPPlus.dll实现将DataTable 中的数据保存到Excel文件

最近研究不安装office也可以保存Excel文件&#xff0c;在网上查询资料找到这个方法。 第一步&#xff1a;下载EPPlus.dll文件&#xff08;自行去网上搜索下载&#xff09; 第二步&#xff1a;引用到需要用的项目中&#xff0c;如图所示&#xff1a; 第三步&#xff1a;写代码…

暑期实习准备:C语言

1.局部变量和全局变量 局部变量的作用域是在变量所在的局部范围&#xff0c;全局变量的作用域是整个工程&#xff1b;局部变量的生命周期是作用域内&#xff0c;全局变量的生命周期是整个程序的生命周期&#xff0c;当两者命名冲突时&#xff0c;优先使用的是局部变量。 2.C语言…

OGG 19C 集成模式启用DDL复制

接Oracle19C PDB 环境下 OGG 搭建&#xff08;PDB to PDB&#xff09;_cdb架构 配置ogg-CSDN博客&#xff0c;给 pdb 环境 ogg 配置 DDL 功能。 一个报错 SYShfdb1> ddl_setup.sqlOracle GoldenGate DDL Replication setup scriptVerifying that current user has privile…

【动态规划】落花人独立,微雨燕双飞 - 8. 01背包问题

本篇博客给大家带来的是01背包问题之动态规划解法技巧. &#x1f40e;文章专栏: 动态规划 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅&#x1f680; 要开心要快乐顺便…

FluentCMS:基于 ASP.NET Core 和 Blazor 技术构建的开源CMS内容管理系统

推荐一个基于 ASP.NET Core 和 Blazor 技术构建的、功能完善的开源CMS内容管理系统。 01 项目简介 FluentCMS 是一个基于强大的 ASP.NET Core 和创新的 Blazor 技术构建的现代内容管理系统&#xff08;CMS&#xff09;。 FluentCMS 设计为快速、灵活且用户友好&#xff0c;它…

[创业之路-262]:《向流程设计要效率》-2-职能型组织、项目型组织、流程型组织的异同比较

目录 一、职能型组织与流程化组织的比较 1.1、定义与结构 1.2、关注焦点与运作方式 1.3、优势与局限性 1.4、转型与发展 二、职能型组织、项目型组织、流程型组织的比较 2.1、定义与特点 2.2、优势与局限性 2.3、适用场景与选择建议 三、项目型组织、流程型组织的异同…

5G网络下移动机器人的图像和指令传输用于远程操作

论文标题 **英文标题&#xff1a;**Image and Command Transmission Over the 5G Network for Teleoperation of Mobile Robots **中文标题&#xff1a;**5G网络下移动机器人的图像和指令传输用于远程操作 作者信息 Thiago B. Levin,, Joo Miguel Oliveira,, Ricardo B. Sou…

云计算、AI与国产化浪潮下DBA职业之路风云变幻,如何谋破局启新途?

引言 在近日举办的一场「云和恩墨大讲堂」直播栏目中&#xff0c;云和恩墨联合创始人李轶楠、副总经理熊军和欧冶云商数据库首席薛晓刚共同探讨了DBA的现状与未来发展。三位专家从云计算、人工智能、国产化替代等多个角度进行了深入的分析和探讨&#xff0c;为从业者提供了宝贵…

30天开发操作系统 第 17 天 -- 命令行窗口

前言 今天一开始&#xff0c;请大家先回忆一下任务A的情形。在harib13e中&#xff0c;任务A下面的LEVEL中有任务因此FIFO为空时我们可以让任务A进入休眠状态。那么&#xff0c;如果我们并未启动任务B0~ B0~ B2, B2的话&#xff0c;任务A又将会如何呢&#xff1f; 首先&#xf…

R语言学习笔记之开发环境配置

一、概要 整个安装过程及遇到的问题记录 操作步骤备注&#xff08;包含遇到的问题&#xff09;1下载安装R语言2下载安装RStudio3离线安装pacman提示需要安装Rtools4安装Rtoolspacman、tidyfst均离线安装完成5加载tidyfst报错 提示需要安装依赖&#xff0c;试错逐步下载并安装…

数据结构 链表2

目录 前言&#xff1a; 一&#xff0c;反转一个链表(迭代) 二&#xff0c;打印一个链表&#xff08;递归&#xff09; 三&#xff0c;反转一个链表(递归) 四&#xff0c;双向链表 总结 前言&#xff1a; 我们根据 [文章 链表1] 可以知道链表相比较于数组的优缺点和计算机…

考研408笔记之数据结构(五)——图

数据结构&#xff08;五&#xff09;——图 1. 图的基本概念 1.1 图的定义 1.2 有向图和无向图 在有向图中&#xff0c;使用圆括号表示一条边&#xff0c;圆括号里元素位置互换没有影响。 在无向图中&#xff0c;使用尖括号表示一条边&#xff0c;尖括号里元素位置互换则表示…

游戏设备升级怎么选?RTX4070独显,ToDesk云电脑更具性价比

过新年、添喜气&#xff01;正逢节期来临不知道各位是否都跟小编一样在考虑购置生活中的各样所需呐&#xff1f; 25年可谓是3A游戏大作之年&#xff0c;例如《GTA6》《文明7》《死亡搁浅2》《刺客信条&#xff1a;影》下半年落地的《塞尔达传说&#xff1a;新篇章》《生化危机9…

C语言初阶牛客网刷题——HJ73 计算日期到天数转换【难度:简单】

1. 题目描述——HJ73 计算日期到天数转换 牛客网OJ题链接 描述 每一年中都有 12 个月份。其中&#xff0c;1,3,5,7,8,10,12 月每个月有 31 天&#xff1b; 4,6,9,11 月每个月有 30 天&#xff1b;而对于 2 月&#xff0c;闰年时有29 天&#xff0c;平年时有 28 天。 现在&am…

【深度学习基础】多层感知机 | 权重衰减

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上&#xff0c;结合当代大数据和大算力的发展而发展出来的。深度学习最重…