多线程JUC:解决线程安全问题——synchronized同步代码块、Lock锁

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:多线程&JUC:线程的生命周期与安全问题
📚订阅专栏:多线程&JUC
希望文章对你们有所帮助

上一部分讲解了面试可能会问的线程的生命周期,并且演示了超卖问题来讲解多线程并发的安全问题,超卖问题这是一个经典例子,这里会解释一下解决的方法。
如果是想要解决集群下的线程安全问题,可以学习我在做Redis项目的时候的解决方法:
Redis:原理速成+项目实战——Redis实战8(基于Redis的分布式锁及优化)
Redis:原理速成+项目实战——Redis实战9(秒杀优化)

感兴趣还可以看看如何使用异步下单来实现秒杀,这些实现其实都跟线程的思想都是相关的:
Redis:原理速成+项目实战——Redis实战10(Redis消息队列实现异步秒杀)

解决线程安全问题——synchronized同步代码块、Lock锁

  • 超卖问题分析
  • 同步代码块
    • 同步代码块的两个小细节
    • 同步方法
    • 探讨StringBuffer与StringBuilder
  • Lock锁

超卖问题分析

在上一篇文章的demo中,发现了线程安全问题,不仅同样的票出现了多次,还出现了超出范围的票。
可以看关键的两条代码:

ticket++;
System.out.println("在卖第" + ticket + "张票");

由于CPU执行代码的过程中,其执行权随时会被其他的线程抢走,所以这样的代码会出现一些问题:假设线程1已经执行完了ticket++,还没来得及执行输出语句,线程2就参与了ticket++的操作,这时候就有可能出现输出同一张票的情况。而当ticket=99的时候,若三个线程同时进入if条件,这时候就很可能出现ticket>100的情况,也就是超卖现象。

同步代码块

由于上述的问题,我们可以想到一个方案,就是当有线程抢夺到CPU执行权的时候,将执行的代码全部锁起来,使得其他线程无法执行代码,这样就不会发生上面的问题。
将其锁起来,需要使用到关键字synchronized,格式如下:

synchronized(){//操作共享数据的代码
}

因此接下来需要编写一下这个锁对象,需要满足以下特点:

1、锁默认打开,有一个线程进去了,锁自动关闭
2、里面的代码全部执行完毕,线程出来,锁自动打开

这个锁对象,只要能保证是唯一的,那么锁对象可以非常随意的去定义,这种方式就叫作同步代码块,代码如下:

public class MyThread extends Thread {static int ticket = 0;//锁对象,一定要是唯一的,可以加static关键字static Object obj = new Object();@Overridepublic void run() {while(true){synchronized (obj) {if(ticket < 100){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正在卖第" + ticket + "张票!");}else{break;}}}}
}

同步代码块的两个小细节

1、synchronize这个关键字,我写到了while里面,这部分是不能写到while外面的,不然的话,就会出现100张票只被1个窗口卖光,显然是不符合现实场景的。

2、锁对象必须要是唯一的,学操作系统的时候就学过临界资源,意思其实是一样的,因此可以发现上面代码中的锁对象obj是加上了static关键字的。除了这种方法,其实更常见的方法是使用字节码对象,因为字节码对象是唯一的,因此上述的锁可以写成:

synchronized (MyThread.class){//...
}

同步方法

如果我们要将一个方法里面的所有方法都锁起来,那就没必要锁代码片段,而是锁住整个方法了。
同步方法,就是把synchronized关键字加到方法上,格式:

修饰符 synchronized 返回值类型 方法名(方法参数) {…}

同步方法有2个特点:

1、同步方法会锁住方法里面所有的代码
2、锁对象不能自己指定,而是java自己默认规定好的:
(1)非静态方法:this
(2)静态方法:当前类的字节码文件对象

3窗口卖100张票的问题也可以用同步方法来解决:

1、定义MyRunnable类,实现Runnable接口,而里面的ticket没必要再设置成静态的了,因为主程序中只会将MyRunnable类创建一次,作为一个参数传递到线程中。

public class MyRunnable implements Runnable{int ticket = 0;@Overridepublic void run() {while (true){if (method()) break;}}//这里的锁对象为this,由于主程序中MyRunnable对象是唯一的,因此锁对象也是唯一的private synchronized boolean method() {if (ticket == 100){return true;}else{try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");}return false;}
}

2、编写测试类代码:

	public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);Thread t3 = new Thread(mr);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}

探讨StringBuffer与StringBuilder

我自己在使用字符串拼接的时候,很喜欢使用StringBuilder,而且也阅读过底层的源码,这是一种效率很高的方式,而如果打开api帮助文档,可以发现StringBuffer和StringBuilder几乎是一样的方法,完成的功能也是一样的,而java为什么要设置两个功能一样的类呢?

打开StringBuffer的底层源码,我们可以发现StringBuffer的所有方法都有带有synchronized关键字,即每个方法都是同步方法:
在这里插入图片描述
而StringBuilder底层是没有这个关键字的,因此StringBuffer在多线程下是安全的,满足了线程同步的特点。

当我们实现需求的时候,如果是多线程的,就使用StringBuffer,否则就使用StringBuilder(StringBuffer是会损耗一些时间的)。

Lock锁

synchronized的锁对象是自动开关的,而Lock锁可以时间手动的开关锁,Lock的实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

Lock中提供了获得锁和释放锁的方法:

void lock():获得锁
void unlock():释放锁

Lock是接口,不能直接实例化,所以要采用它的实现类ReentrantLock来实例化,直接使用它的空参构造即可。

使用Lock锁,则MyRunnable类(若是MyThread类,由于会被创建多次,锁又必须要唯一,那么Lock前面就得加上static)应修改为:

public class MyRunnable implements Runnable{int ticket = 0;Lock lock = new ReentrantLock();@Overridepublic void run() {while (true){lock.lock();if (ticket == 100){break;}else{try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");}lock.unlock();}}
}

这样写,线程安全问题确实不会发生,但是程序却没办法终止。
因为当我们的票数为100时,我们直接break跳出循环了,所以没有执行释放锁的语句,其他的线程就在while循环里面一直等待锁的释放,这显然不合理,一种简单的解决方法是在if里面继续加一条释放锁的语句:

if(ticket == 100){lock.unlock();break;
}

这样的方式固然可行,但是这写了两次unlock不是很符合规范。
更规范的方式是使用try...catch...finally,无论如何,程序最终都必须要执行finally里面的语句,上述代码最终可以改写为:

public class MyRunnable implements Runnable{int ticket = 0;Lock lock = new ReentrantLock();@Overridepublic void run() {while (true){try {lock.lock();if (ticket == 100){break;}else{try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");}} catch (RuntimeException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}

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

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

相关文章

【DC渗透系列】DC-4靶场

主机发现 arp-scan -l┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:6b:ed:27, IPv4: 192.168.100.251 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.100.1 00:50:56:c0:00:08 …

记录 | python list extend()

extend() 函数用于在列表末尾一次性追加另一个序列中的多个值&#xff08;用新列表扩展原来的列表&#xff09;。 以下实例展示了 extend()函数的使用方法&#xff1a; #!/usr/bin/pythonaList [123, xyz, zara, abc, 123]; bList [2009, manni]; aList.extend(bList)print …

大厂聚合支付系统架构演进(下)

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 关注我&#xff0c;紧跟本系列专栏文章&#xff0c;咱们下篇再续&#xff01; 作者简介&#xff1a;魔都国企技术专家兼架构&#xff0c;多家大厂后端一线研发经验&#xff0c;各大技术社区…

VMware17上安装centos7.9

一、下载安装包&#xff1a; 1、VMware安装 VMware 下载地址&#xff1a; https://www.vmware.com/cn/products/workstation-pro.html VMware下载后安装即可 安装教程可以参考VMware安装教程 2、CentOs7.9下载地址&#xff1a; http://mirrors.aliyun.com/centos/7.9.2009/iso…

揭秘实战胜利秘籍!2023冬季波卡黑客松获奖团队经验分享全回顾

2023 冬季波卡黑客松大赛 已在香港圆满结束&#xff01;自 2023 年 11 月 1 日报名通道开启以来&#xff0c;2023 冬季波卡黑客松大赛一直备受全球 Web3 爱好者的瞩目。本届波卡黑客松大赛报名人数达到 342 人&#xff0c;总参赛项目高达 111 个&#xff0c;相比上一届增长 39%…

股票均线的使用方法和实战技术,看涨看空的均线形态与案例教学

一、教程描述 本套教程讲解了14种均线的特殊形态&#xff0c;通过直观图形以及大量案例的教学&#xff0c;将深奥、繁琐的均线变得生动与具体&#xff0c;广大投资者在认真学习以后&#xff0c;可以学会均线的使用方法&#xff0c;掌握最强的均线应用实战技术。本套教程不仅适…

freeRTOS总结(十四)任务通知

1、任务通知 任务通知&#xff1a; 用来通知任务的&#xff0c;任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值 使用队列、信号量、事件标志组时都需另外创建一个结构体&#xff0c;通过中间的结构体进行间接通信&#xff01; 使用任务通知时&#xff0c;任务结…

【错误收录】ohpm ERROR: Install failed FetchPackageInfo: @ohos/hypium failed

创建APP的时候出现这样一个错误&#xff0c;是代理没有配置的原因 ohpm.bat install --registry https://repo.harmonyos.com/ohpm/ ohpm WARN: ETIMEDOUT Failed to search for package "ohos/hypium" from "https://repo.harmonyos.com/ohpm/", request…

教你怎么前端实现埋点上报

公众号&#xff1a;程序员白特&#xff0c;可加前端技术交流群 前言 只有了解用户&#xff0c;我们才能服务好用户&#xff0c;而最接近用户的我们&#xff0c;自然要承担起更多的责任。 那么在一个企业中&#xff0c;我们要如何去了解用户呢&#xff1f; 最直接有效的方式就是…

xinput1_3.dll丢失怎么办?7种不同解决方法分享

xinput1_3.dll是微软Microsoft DirectX的一个重要动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它主要与DirectInput API相关&#xff0c;为Windows操作系统中的游戏和应用程序提供对各种输入设备的支持。以下是关于xinput1_3.dll的详细全面介绍&#xff1a; 1、属性…

如何开始深度学习,从实践开始

将“如何开始深度学习”这个问题喂给ChatGPT和文心一言&#xff0c;会给出很有专业水准的答案&#xff0c;比如&#xff1a; 要开始深度学习&#xff0c;你可以遵循以下步骤&#xff1a; 学习Python编程语言的基础知识&#xff0c;因为它在深度学习框架中经常被使用。 熟悉线性…

力扣hot100 -- 哈希

目录 &#x1f33c;两数之和 暴力 二分 哈希 &#x1f33c;字母异位词分组 unordered_map 排序 unordered_map 计数 &#x1f33c;最长连续序列 unordered_set 跳过前驱 排序 dp &#x1f33c;两数之和 1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 暴…

春运也要“信号升格”:中兴通讯助运营商打造高铁精品网

一年一度的春运&#xff0c;承载了游子的思乡情。据官方预计&#xff0c;今年春运跨区域人员流动量将达到90亿人次&#xff0c;创下历史新高&#xff0c;铁路、公路、水路、民航等营业性客运量全面回升&#xff0c;其中铁路预计发送旅客4.8亿人次&#xff0c;日均1200万人次&am…

rust语言tokio库底层原理解析

目录 1 rust版本及tokio版本说明1 tokio简介2 tokio::main2.1 tokio::main使用多线程模式2.2 tokio::main使用单线程模式 3 builder.build()函数3.1 build_threaded_runtime()函数新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图…

.NET Core 实现 JWT 认证

写在前面 JWT&#xff08;JSON Web Token&#xff09;是一种开放标准, 由三部分组成&#xff0c;分别是Header、Payload和Signature&#xff0c;它以 JSON 对象的方式在各方之间安全地传输信息。通俗的说&#xff0c;就是通过数字签名算法生产一个字符串&#xff0c;然后在网络…

使用Python进行数据的描述性分析,用少量的描述性指标来概括大量的原始数据

在进行数据分析时&#xff0c;当研究者得到的数据量很小时&#xff0c;可以通过直接观察原始数据来获得所有的信息。但是&#xff0c;当得到的数据量很大时&#xff0c;就必须借助各种描述性指标来完成对数据的描述工作。用少量的描述性指标来概括大量的原始数据&#xff0c;对…

计算机网络相关题目及答案(第五章)

第五章 复习题: R2. 基于逻辑上集中控制的控制平面意味着什么?在这种有情况下,数据平面和控制平面是在相同的设备或在分离的设备中实现的吗?请解释。 答:基于逻辑上集中控制的控制平面意味着控制平面的具体实现不在每个路由器中, 而是在某个集中的地方(服务器). 这种情…

廖雪峰Python教程实战Day 2 - 编写Web App骨架,运行后不显示网页如何解决

教程代码如下&#xff1a; import logging; logging.basicConfig(levellogging.INFO)import asyncio, os, json, time from datetime import datetimefrom aiohttp import webdef index(request):return web.Response(bodyb<h1>Awesome</h1>)asyncio.coroutine de…

【每日一题】LeetCode——链表的中间结点

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…

【数据结构】二叉树的三种遍历(非递归讲解)

目录 1、前言 2、二叉树的非递归遍历 2.1、先序遍历 2.2、中序遍历 2.3、后序遍历 1、前言 学习二叉树的三种非递归遍历前&#xff0c;首先来了解一下递归序&#xff1a; 递归序就是按照先序遍历的顺序&#xff0c;遇到的所有结点按顺序排列&#xff0c;重复的结点也必须记…