【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.hqwc.cn/news/317209.html

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

相关文章

Android Matrix剪切clipPath缩放scale图片postTranslate圆形放大镜,Kotlin(1)

Android Matrix剪切clipPath缩放scale图片postTranslate圆形放大镜&#xff0c;Kotlin&#xff08;1&#xff09; 实现查看图片的放大镜&#xff0c;放大镜随着手指在屏幕上的移动&#xff0c;放大镜里面展示手指触点为中心、半径长度的圆形放大后的图片。 剪切出一块圆形Path…

掌握静态S5:从入门到精通的指南

在现今的数据驱动时代&#xff0c;静态S5作为一款强大的数据分析工具&#xff0c;越来越受到各行各业的青睐。然而&#xff0c;如何从入门到精通&#xff0c;全面掌握静态S5的各项功能&#xff0c;成为了许多用户面临的挑战。本文将为你提供一份详尽的指南&#xff0c;助你顺利…

milvus学习(一)cosin距离和欧式距离

参考&#xff1a;https://blog.csdn.net/qq_36560894/article/details/115408613 归一化以后的cosin距离和欧式距离可以相互转化&#xff0c;未归一化的不可以相互转化&#xff08;因为距离带单位&#xff09;。

一个人去广东怎么找工作

广东这么大&#xff0c;不用怕没有学历活不下去。没有学历想好好活下去&#xff0c;就得卖力气。 广东找工作上 吉鹿力招聘网 打开 吉鹿力招聘网 “注册账号”&#xff0c;然后输入个人基本信息&#xff0c;进行注册&#xff08;可使用手机号注册&#xff0c;也可以使用邮箱注…

新闻稿件发稿:新闻稿的5大长远意义

在信息爆炸时代&#xff0c;企业品牌如何才能从海量的信息中可以脱颖而出&#xff0c;企业的成功与否不仅仅取决于产品质量或服务水平&#xff0c;更在于如何通过有效的传播手段塑造品牌形象、建立公众信任。 新闻稿是一种用于传达新闻信息的书面文本&#xff0c;通常由编辑、…

【C++入门】类和对象(完)

前言 在谈论C时&#xff0c;常常会涉及到一些高级特性和概念&#xff0c;比如初始化列表、static成员、友元、内部类、匿名对象等。这些概念在C编程中起着非常重要的作用&#xff0c;对于想要深入了解C语言的开发者来说&#xff0c;掌握这些知识是至关重要的。本文&#xff0c;…

桌面天气预报软件 Weather Widget free mac特点介绍

Weather Widget free for Mac多种吸引人的小部件设计可供选择&#xff0c;可以随时了解天气&#xff01;还可以在Dock和菜单栏中为您提供简短的天气预报或当前状况的概述。 Weather Widget free for Mac软件介绍 始终在桌面上使用时尚的天气小部件来随时了解天气&#xff01;多…

算法29:不同路径问题(力扣62和63题)--针对算法28进行扩展

题目&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff0…

如何运行Python程序

1、打开python可执行文件的目录&#xff0c;双击可执行文件 2、在第一个页面&#xff0c;首选勾选下面的Add Python 3.6 to PATH ,将python加到windows环境中 3、如果你想快速的安装的话&#xff0c;可以直接点击Install Now但是笔者这里选择Customize installation 自定义安…

高效管理版本控制,Cornerstone 4 for Mac助您成为SVN专家

在软件开发和团队合作中&#xff0c;版本控制是一个至关重要的环节。为了帮助开发者更加高效地管理和控制代码版本&#xff0c;Cornerstone 4 for Mac应运而生。作为一款功能强大的SVN&#xff08;Subversion&#xff09;管理工具&#xff0c;Cornerstone 4 for Mac为Mac用户提…

多任务并行处理相关面试题

我自己面试时被问过两次多任务并行相关的问题&#xff1a; 假设现在有10个任务&#xff0c;要求同时处理&#xff0c;并且必须所有任务全部完成才返回结果 这个面试题的难点是&#xff1a; 既然要同时处理&#xff0c;那么肯定要用多线程。怎么设计多线程同时处理任务呢&…