【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.mzph.cn/news/593221.shtml

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

相关文章

Sharding Sphere 教程 简介

一 文档简介 1.1 分库分表诞生的前景 随着系统用户运行时间还有用户数量越来越多&#xff0c;整个数据库某些表的体积急剧上升&#xff0c;导致CRUD的时候性能严重下降&#xff0c;还容易造成系统假死。 这时候系统都会做一些基本的优化&#xff0c;比如加索引…

高德地图经纬度坐标导出工具

https://tool.xuexiareas.com/map/amap 可以导出单个点&#xff0c;也可以导出多个&#xff0c;多个点可以连成线&#xff0c;可用于前端开发时自己模拟“线“数据

基于Springboot的服务端开发脚手架-自动生成工具

继之前的 专题系列课程&#xff1a; ​​从零开始搭建grpc分布式应用​​完整DEMO&#xff1a;​​基于Springboot的Rpc服务端开发脚手架(base-grpc-framework)​​ 后带来一款项目自动手成工具&#xff08;由于包路径等原因&#xff0c;完整demo想应用在实际开发中需要改很多代…

编程笔记 html5cssjs 008 HTML图片

编程笔记 html5&css&js 008 HTML图片 一、HTML 图像二、HTML 图像- Alt属性三、HTML 图像- 设置图像的高度与宽度四、支持的图像格式五、对齐设置六、操作小结 和印刷品一样&#xff0c;网页上不仅要有文字&#xff0c;还要有图片和声音、视频、绘画等媒体形式。这一节…

广州求职招聘(找工作)去哪里找比较好

在广州找工作&#xff0c;可以选择“吉鹿力招聘网”这个平台。它是一个号称直接和boss聊的互联网招聘神器&#xff0c;同时&#xff0c;“吉鹿力招聘网”作岗位比较齐全&#xff0c;企业用户也多&#xff0c;比较全面。在“吉鹿力招聘网”历即可投递岗位。 广州找工作上 吉鹿力…

学习的记录

一、内核安装 1.安装内核编译工具 install gcc gcc-c ncurses ncurses-devel cmake elfutils-libelf-devel openssl-devel 将内核源码linux-4.14.160.tar拷贝到/usr/src/kernels目录下 cp -r linux-4.20.2.tar.xz /usr/src/kernels 2.进入内核源码所在的文件夹&#xff1…

结构体与函数简单总结(依靠洛谷结构体题与函数题单)

函数结构体简单总结 依靠洛谷函数与字符串题单 文章目录 函数结构体简单总结前言一、函数1、有返回值的函数2、无返回值函数3、递归函数 二、结构体总结 前言 之前总结了字符串的简单应用&#xff0c;随着函数与结构体的题单完成&#xff0c;入门题单也就刷完了&#xff0c;现…

你可能不知道的5款好用封面设计工具,快来一探究竟吧!

我相信每个作者和出版商都希望在一部作品完成后有一个醒目的封面&#xff0c;这样潜在的读者就会有足够的好奇心拿起这本书&#xff0c;你的书的销量就会上升。这就是封面设计软件的使用&#xff0c;专业的封面设计软件可以增加前沿效果&#xff0c;呈现最适合书籍内容的创意布…

Tailscale:随时随地远程和使用服务器

Tailscale是什么&#xff1f; 网上有时候提到tailscale&#xff0c;总是介绍好多&#xff0c;比如以下介绍&#xff0c;但是太官方了 Tailscale 是一种基于 WireGuard 的虚拟组网工具&#xff0c;和 Netmaker 类似&#xff0c;最大的区别在于 Tailscale 是在用户态实现了 Wire…

NSSCTF 1zjs

开启环境: 搞就完事了,别玩魔法! 源码打开 点击访问:./dist/index.umd.js" 搜索php,找到23条相关的,注意到有一个特别的信息: PERFORMANCE OF THIS SOFTWARE.Your gift just take it : /fk3f1ag.php 访问: node4.anna.nssctf.cn:28325/fk3f1ag.php 得到这样: ([![]…

【每日试题】精选Java面试题八股文

题目1&#xff1a;什么是Java的泛型&#xff1f;请举例说明。 答案&#xff1a;Java的泛型是一种参数化类型的概念&#xff0c;可以在类、接口和方法中使用。使用泛型可以在编译时进行类型检查&#xff0c;提高程序的安全性和可读性。例如&#xff0c;可以定义一个泛型类List来…

ubuntu系统上安装virtualenv后报错“Command ‘virtualenv‘ not found”

前言 Ubuntu系统上&#xff0c;由于不同用户可能会导致依赖包安装后&#xff0c;无法正常加载命令&#xff0c;就比如&#xff0c; pip3 install virtualenv后&#xff0c;报错&#xff1a;Command virtualenv not found, but can be installed with 这是由于当前用户安装依赖…

【高录用-快速见刊-EI稳定检索】2024年计算机建模与信号处理国际学术会议(ICCMSP 2024)

【高录用-快速见刊-EI稳定检索】2024年计算机建模与信号处理国际学术会议&#xff08;ICCMSP 2024&#xff09; 2024 International Conference on Computer Modeling and Signal Processing 一、【会议简介】 尊敬的各位专家、学者和与会嘉宾&#xff1a; 欢迎来到2024年计算…

opencv期末练习题(3)附带解析

创建黑色画板&#xff0c;并支持两种画图功能 import mathimport cv2 import numpy as np """ 1. 创建一个黑色画板 2. 输入q退出 3. 输入m切换画图模式两种模式&#xff0c;画矩形和画圆形。用户按住鼠标左键到一个位置然后释放就可以画出对应的图像 "&qu…

Could not resolve all dependencies for configuration ‘:classpath‘

报错解析&#xff1a; 问题是与构建配置和依赖解析有关的&#xff0c;特别是在使用Maven或Gradle这样的构建工具时 报错解决&#xff1a; 该方法在网上经过多个参考后认为对我自己有用的方法&#xff0c;就是将maven镜像的http改为https

静态库与动态库

这里写目录标题 静态库与动态库简介 静态库简图制作与使用总览制作 二级目录 动态库&#xff08;共享库&#xff09;简图二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 静态库与动态库 简介 静态库&…

python 画图转化为html

优点&#xff1a;画图转化为html可以手动拖动。并且可以放大缩小 示例一 import matplotlib.pyplot as plt import mpld3# 准备数据和图表 x [1, 2, 3, 4, 5] y [2, 3, 5, 7, 11]fig, ax plt.subplots(figsize (10,10)) ax.plot(x, y, o-, labelData Points) ax.set_xlabe…

访问学者J1签证的申请流程

访问学者J1签证是许多人前往美国进行学术研究和文化交流的重要途径之一。申请J1签证需要经过一系列步骤和程序&#xff0c;让知识人网小编带大家来了解一下申请流程吧。 首先&#xff0c;申请者需要确认自己符合J1签证的资格要求。这包括被美国的赞助机构或组织接受&#xff0c…

微前端 Micro App

MicroApp 官网链接 MicroApp 链接

使用jieba库进行中文分词和去除停用词

jieba.lcut jieba.lcut()和jieba.lcut_for_search()是jieba库中的两个分词函数&#xff0c;它们的功能和参数略有不同。 jieba.lcut()方法接受三个参数&#xff1a;需要分词的字符串&#xff0c;是否使用全模式&#xff08;默认为False&#xff09;以及是否使用HMM模型&…