【Java并发】深入浅出 synchronized关键词原理-上

一个问题的思考

建设我们有两个线程,一个进行5000次的相加操作,另一个进行5000次的减操作。那么最终结果是多少

package com.jia.syn;import java.util.concurrent.TimeUnit;/*** @author qxlx* @date 2024/1/2 10:08 PM*/
public class SynTest {private Integer tickets = 0;public void sell() {tickets++;}public void sell2() {tickets--;}public static void main(String[] args) throws InterruptedException {SynTest synTest = new SynTest();Thread thread1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synTest.sell();}});Thread thread = new Thread(() -> {for (int i = 0; i < 5000; i++) {synTest.sell2();}});thread1.start();thread.start();thread.join();thread1.join();TimeUnit.SECONDS.sleep(3);System.out.println("总共卖出多少票" + synTest.tickets);}}

执行上述代码之后,发现结果却不是0,为什么

 7 getfield #3 <com/jia/syn/SynTest.tickets : Ljava/lang/Integer;>
10 invokevirtual #4 <java/lang/Integer.intValue : ()I>
13 iconst_1
14 iadd
15 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
18 dup_x1

通过分析字节码变量,可以看出其实i++, i–操作其实是三个步骤,也就是先获取i的值,对i+1操作,然后在对i赋值。那么这样就可以解释为什么执行的最终结果不是期望的0值。

程序在执行的时候,不同的两个线程执行。比如会出现线程2获取到i的值是10,对i-1操作9,但是想要将i=9赋值操作的时候,发现CPU执行权被线程1获取,此时线程1获取到i的值是10,对i+1操作,然后复制给i=11。但是紧接着就是线程2对i=9赋值。所以最终出现的结果就是9,而不是 原来的11。将线程1的值进行覆盖更新了。
在这里插入图片描述

临界区

上述其实是多个线程对于共享资源进行读写操作,导致出现数据不一致。如果是只读,那没有问题,但是有写操作,就会出现乱序问题。
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,其共享资源为临界资源
上述中的

    public void sell() { // 临界区tickets++;}public void sell2() { //临界区tickets--;}

为了解决上述的问题,那么可以使用多种手段进行解决。

  • 阻塞式:synchronized、lock
  • 非阻塞式:原子变量

Synchronized

在这里插入图片描述

    private Object obj = new Object();// 方法//静态方法-锁住的类对象public static synchronized void test1() {}//普通方法-锁住的对象实例public synchronized void test2(){}//代码块public void test3 (){//代码块-锁住的是该类对象synchronized (SynTest2.class) {}}//代码块public void test4 (){//代码块-锁住的是该对象实例synchronized (this) {}}//代码块public void test5 (){//代码块-锁住的是该obj对象实例synchronized (obj) {}}

所以解决上述的问题,就可以加syn锁。

原理

synchronized是JVM内置锁,基于Monitor机制实现,依赖底层操作系统的互斥原语
Mutex(互斥量),它是一个重量级锁,性能较低。当然,JVM内置锁在1.5之后版本做了重大的 优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、自适应自旋(Adaptive Spinning)等技术来减少锁操 作的开销,内置锁的并发性能已经基本与Lock持平。

同步方法是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现;同步代码块是通过monitorenter和monitorexit来实现。两个指令的执行是JVM通过调用操作系统的互斥 原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态 之间来回切换,对性能有较大影响。

同步方法

在这里插入图片描述

同步代码块

 0 aload_01 dup2 astore_13 monitorenter //进入4 aload_15 monitorexit //退出6 goto 14 (+8)9 astore_2
10 aload_1
11 monitorexit //异常退出
12 aload_2
13 athrow
14 return

管程

管程,其实是管理共享变量以及对共享变量操作过程。英文是Monitor,也叫监视器。
管程有三种不同的管程模型,Hasen模型、Hoare模型、MESA模型。目前主要用的后者。

并发编程中,互斥解决的是对于共享资源同时只能有一个线程访问,同步是线程之间如何通信、协作的问题。管程都可以解决。
在这里插入图片描述
条件变量等待队列解决的是同步问题,入口等待队列解决的是互斥问题。

java中对管程的实现进行了精简,只有一个条件变量等待队列。
在这里插入图片描述

java.lang.Object 类定义了 wait(),notify(),notifyAll() 方法,这些方法的具体实现,依赖
于 ObjectMonitor 实现,这是 JVM 内部基于 C++ 实现的一套机制。 ObjectMonitor其主要数据结构如下(hotspot源码ObjectMonitor.hpp)

ObjectMonitor(){
2 _header = NULL; //对象头 markOop
3 _count = 0;
4 _waiters = 0,
5 _recursions = 0; // 锁的重入次数
6 _object = NULL; //存储锁对象
7 _owner = NULL; // 标识拥有该monitor的线程(当前获取锁的线程)
8 _WaitSet = NULL; // 等待线程(调用wait)组成的双向循环链表,_WaitSet是第一个节点
9 _WaitSetLock = 0 ;
10 _Responsible = NULL ;
11 _succ = NULL ;
12 _cxq = NULL ; //多线程竞争锁会先存到这个单向链表中 (FILO栈结构)
13 FreeNext = NULL ;
14 _EntryList = NULL ; //存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失
败的线程)
15 _SpinFreq = 0 ;
16 _SpinClock = 0 ;
17 OwnerIsThread = 0 ;
18 _previous_owner_tid = 0;
19 }

我们主要关注的其实是waitSet,cxq 、EntryList。

1.多个线程竞争获取锁
多个线程同时请求获取Monitor锁时,会通过CAS操作,设置_owner字段。谁设置成功,就获取锁。

2.没有获取锁的线程排队等待获取锁
多个线程获取锁,获取到锁的线程就去执行任务,没有获取到锁的线程会进入到_cxq队列中等待获取锁。

3.获取到锁之后通知排队等待锁的线程去竞争锁
当执行完线程的释放锁的时候,会从_EntryLitst取出一个线程,去通过CAS竞争锁,之所以不让这个线程获取锁而去竞争锁,是因为同时可能有别的线程可能获取到锁。

如果_EntryList队列为空的话,那么将_cxq所有线程全部搬移到_EntryList中。在中_EntryList中获取线程。

在这里插入图片描述
另外就是当调用 Object.wait() 会进入 _WaitSet 队列,只要被唤醒时,才会重新进入 EntryList 中去增强锁。

在这里插入图片描述

总结

本篇主要通过一个案例讲解了线程安全问题,以及介绍了syn代码块和方法底层实现的区别,以及介绍了管程、java中实现管程的方式。下一篇文章,开始介绍syn的锁升级。

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

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

相关文章

稳部落 – 新浪微博备份导出工具

稳部落 稳部落是新浪微博备份导出工具&#xff0c;可以帮助用户非常方便的导出备份新浪微博的数据&#xff0c;让我们可以永久保存这些微博数据。它支持新浪微博、微博私信、微博评论的导出&#xff0c;并可以备份包含图片、视频的完整微博内容。用户只需登录微博账号&#xf…

Java学习,一文掌握Java之SpringBoot框架学习文集(2)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

【计算机毕业设计】SSM游戏点评网站

项目介绍 本项目分为前后台&#xff0c;前台为普通用户登录&#xff0c;后台为管理员登录&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,管理员管理,网站用户管理,游戏资讯管理,游戏类型管理,城市信息管理,竞技场管理,游戏信息管理,游戏评价信息管理等功能。…

双侧电源系统距离保护MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 系统原始数据 双侧电源系统模型如图所示&#xff1a; 仿真模型搭建 将线路AB分成Line1和Line2&#xff0c;将线路BC分成Line3和Line4&#xff0c;用三相电压电流测量模块作为系统母线&#xff0c;根据系统已…

Java基础-----集合类(一)

文章目录 1.集合类简介2. 自定义集合类 1.集合类简介 集合和数组一样&#xff0c;都是用来存储多个数据的结构&#xff0c;也可以称作容器。 数组长度是不可变化的&#xff0c;一旦在初始化数组时指定了数组长度&#xff0c;这个长度就不可变。如果需要处理数量变化的数据&am…

Flutter 混合开发 - 动态下发 libflutter.so libapp.so

背景 最近在做包体积优化&#xff0c;在完成代码混淆、压缩&#xff0c;裁剪ndk支持架构&#xff0c;以及资源压缩&#xff08;如图片转webp、mp3压缩等&#xff09;后发现安装包的中占比较大的仍是 so 动态库依赖。 具体查看发现 libflutter.so 和 libapp.so 的体积是最大的&…

探索Java的魅力

从本篇文章开始&#xff0c;小编准备写一个关于java基础学习的系列文章&#xff0c;文章涉及到java语言中的基础组件、实现原理、使用场景、代码案例。看完下面一系列文章&#xff0c;希望能加深你对java的理解。 本篇文章作为本系列的第一篇文章&#xff0c;主要介绍一些java…

【数据库原理】(6)关系数据库的关系操作集合

基本关系操作 关系数据操作的对象都是关系,其操作结果仍为关系,即集合式操作。关系数据库的操作可以分为两大类&#xff1a;数据查询和数据更新。这些操作都是基于数学理论&#xff0c;特别是集合理论。下面是对这些基本操作的解释和如何用不同的关系数据语言来表达这些操作的…

STM32入门教程-2023版【3-2】推挽输出和开漏输出驱动问题

关注 点赞 不错过精彩内容 大家好&#xff0c;我是硬核王同学&#xff0c;最近在做免费的嵌入式知识分享&#xff0c;帮助对嵌入式感兴趣的同学学习嵌入式、做项目、找工作! 二、正式点亮一个LED灯 &#xff08;4&#xff09;推挽输出和开漏输出驱动问题 把LED的正负极对换&…

react useEffect 内存泄漏

componentWillUnmount() {this.setState (state, callback) > {return;};// 清除reactionthis.reaction();}useEffect 使用AbortController useEffect(() > { let abortController new AbortController(); // your async action is here return () > { abortCo…

008、所有权

所有权可以说是Rust中最为独特的一个功能了。正是所有权概念和相关工具的引入&#xff0c;Rust才能够在没有垃圾回收机制的前提下保障内存安全。 因此&#xff0c;正确地了解所有权概念及其在Rust中的实现方式&#xff0c;对于所有Rust开发者来讲都是十分重要的。在本文中&…

添加 Android App Links

添加 Android App Links功能 介绍一个简单的效果Android配置Add Url intent filtersAdd logic to handle the intentAssociate website 搭建网页支持AppLinks 介绍 Android App Links 是指将用户直接转到 Android 应用内特定内容的 HTTP 网址。Android App Links 可为您的应用带…

计算机网络--作业

作业一 1、比较电路交换、报文交换和分组报文交换优缺点 电路交换 电路交换是以电路连接为目的的交换方式&#xff0c;通信之前要在通信双方之间建立一条被双方独占的物理通道&#xff08;由通信双方之间的交换设备和链路逐段连接而成&#xff09;。 优点&#xff1a; ①由于…

SpringSecurity-2.7中跨域问题

SpringSecurity-2.7中跨域问题 访问测试 起因 写这篇的起因是会了解到 SSM(CrosOrigin)解决跨域,但是会在加入SpringSecurity配置后,这个跨域解决方案就失效了,而/login这个请求上是无法添加这个注解或者通过配置(WebMvcConfig)去解决跨域,所以只能使用SpringSecurity提供的.c…

MySQL的安装网络配置

目录 一. MySQL5.7的安装 二. MySQL8.0的安装 三. 配置网络访问 思维导图 一. MySQL5.7的安装 1. 解压 2. 将my.ini文件放入到解压文件中 3. 编辑my.ini文件&#xff0c;将路径改为当前路径 4. 进到bin目录下&#xff0c;以管理员身份打开cmd命令窗口 5. 安装MySQL服务 my…

C++Qt6 哈夫曼编码求解 数据结构课程设计 | JorbanS

一、 问题描述 在进行程序设计时&#xff0c;通常给每一个字符标记一个单独的代码来表示一组字符&#xff0c;即编码。在进行二进制编码时&#xff0c;假设所有的代码都等长&#xff0c;那么表示 n 个不同的字符需要 位&#xff0c;称为等长编码。如果每个字符的使用频率相等&…

【hyperledger-fabric】部署和安装

简介 对hyperledger-fabric进行安装&#xff0c;话不多说&#xff0c;直接开干。但是需要申明一点&#xff0c;也就是本文章全程是开着加速器进行的资源操作&#xff0c;所以对于没有开加速器的情况可能会由于网络原因导致下载资源失败。 资料提供 1.官方部署文档在此&#…

车载电子电器架构 —— 电子电气系统开发角色定义

车载电子电器架构 —— 电子电气系统开发角色定义 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 注:本文12000字,深度思考者进!!! 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的…

Redis7.2.3(Windows版本)

1、解压 &#xfeff; &#xfeff; 2、设置密码 &#xff08;1&#xff09; 右击编辑redis.conf文件&#xff1a; &#xfeff; &#xff08;2&#xff09; 设置密码。 &#xfeff; 3、测试密码是否添加成功 &#xfeff; 如上图所示&#xff0c;即为成功。 4、设置…