关于Java中synchronized的实现原理

并发编程的三个理念

  • 原子性:一个操作要么全部完成,要么全部失败。
  • 可见性:当一个线程对共享变量进行修改后,其他线程也应立刻看到。
  • 有序性:程序按照顺序执行

synchronized基本使用

  • 修饰静态方法,锁的是类,Class字节码对象
  • 修饰实例方法,锁的是当前实例对象
  • 修饰代码块,锁的是当前指定的对象

原理

在JDK1.6之前,synchronized是重量级锁,是独占锁,在JDK6中,引入了偏向锁和轻量级锁,同时synchronized支持锁升级,降低了synchronized的性能消耗。
我们以synchronized的重量级锁为例,来讲解原理。

同步代码块

当一个线程访问同步代码块时,首先要获取到锁才能执行同步代码块,当退出或抛出异常时必须要释放锁
我们这里有一个同步代码块

public void add() {synchronized (this) {int i = 1;int b = i + 3;}
}

利用javap反编译看这段代码的字节码指令

可以看到,synchronized是通过monitorenter和monitorexit这一组字节码指令来完成对临界资源的互斥访问

  • monitorenter标志进入同步代码块
  • monitorexit标志退出同步代码块。

我们知道,任意一个对象都可以作为锁对象。
每个锁对象都有一个监视器,叫做Monitor,当Monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取Monitor的使用权,过程如下

  1. 如果Monitor的进入数是0,则该线程进入Monitor,然后将进入量设置为1,该线程即为Monitor的所有者。
  2. 如果线程已经占用了该Monitor,只是重新进入,则进入Monitor的进入数加1
  3. 如果其他线程已经占用了Monitor,则该线程进入阻塞状态,直到Monitor的进入数为0,在重新尝试获取Monitor的所有权。

Monitor是操作系统中管程的一个实现,管程是操作系统中对同步互斥的一种实现方案,建议先去看看管程。

执行monitorexit的线程必须是锁对象所对应的Monitor的所有者,线程执行monitorexit指令的过程:
monitorexit执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出Monitor,不再是这个Monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

这一组指令必须是成对出现的,不能单独出现,这两个指令是通过操作系统互斥原语mutex来实现的,被阻塞的线程会被挂起、等待重新调度,会导致用户态和内核态之间的来回切换,性能损耗严重。

总结,synchronized底层是通过一个Monitor监视器对象来实现的,线程只有抢占到了Monitor对象的所有权,才有权获取到临界资源,线程之间的wait/notify等方法也是依赖于monitor对象,这就是为什么只有在同步代码块中执行wait/notify,否则抛出异常

同步方法

对于同步方法,即在方法上使用synchronized关键字修饰,在反编译后,并没有看到monitorenter和monitorexit这一组指令。
这是一个同步方法

public synchronized void add() {int i = 1;int b = i + 3;
}

利用javap 反编译后的结果中,没有monitorenter和monitorexit这一对字节码指令

对于同步方法来说,在常量池中多了一个ACC_SYNCHRONIZED标志位,用来标记该方法是否是一个同步方法,JVM会检查该标志位来完成方法的同步:
当方法被调用时,首先会检查该方法的ACC_SYNCHRONIZED标志位是否被设置

  • 如果设置了,表明该方法是一个同步方法,会先去持有Monitor,然后执行方法体。
  • 在方法执行期间,其他线程都无法获取到同一个Monitor。
  • 如果在方法执行期间发生了无法处理的异常,那么在抛出异常时,会自动释放该Monitor。

无论是同步方法还是同步代码块,这两种同步的本质是没有区别的,都是通过Monitor监视器对象来实现的。

ObjectMonitor

在JVM中,Monitor是由ObjectMonitor实现的,
ObjectMonitor整体上分为两部分,一部分是是这个监控对象的基本信息,表示当前锁的实时状态,一部分表示各种情况下需要获取锁的排队信息。如图所示:

具体的工作流程是:

  1. 每个等待锁的线程会被封装成ObjectWaiter对象,当线程需要获取Object Monitor时,将线程封装成ObjectWaiter对象,放入Entry Set集合中。
  2. 当线程获取到ObjectMonitor后,就可以获取到临界资源了,同时ObjectMonitor内部的owner属性指向此线程。每个ObjectMonitor同一时刻只有一个线程进入。
  3. 如果线程获取到了ObjectMonitor之后,在执行过程中调用了wait()或wait(timeout)方法,则当前线程进入到wait Set集合,并释放持有的ObjectMonitor。
  4. 当其他线程进入ObjectMonitor之后,调用notify()或notifyAll(),则wait Set中的线程会被唤醒,重新进入Entry Set去争夺ObjectMonitor
    大致的工作流程就是这样。

synchronized的可重入性

从互斥锁的设计上看,当一个线程试图操作另一个线程持有的锁临界资源时,会进入阻塞状态;
当一个线程再次请求自己持有对象锁的临界资源时,请求就会成功。
在Java中,synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,是可以的

线程获取到了锁之后,再次请求该锁对象的临界资源,是允许的,这就是synchronized的可重入性。
例如:

class MyRun{int i = 0;int j = 100;public synchronized void add(){i ++;increase(); // 再次获取该对象锁,直接允许,因为当前线程已经持有了该锁}public synchronized void increase(){j --;}
}

当线程执行到了add()方法,说明该线程已经拥有了该锁,在add()方法中,再次请求increase()方法,因为该方法的锁已经被当前线程持有了,所以直接允许,操作成功,这就是可重入性。

synchronized的优化

锁的状态共有四种:无锁、偏向锁、轻量级锁、重量级锁。
随着线程的竞争激励程度增加,锁可以从偏向锁升级到轻量级锁,再升级到重量级锁,锁的升级是单向的,只能从低到高升级,不会出现锁的降级
可以看我的这篇文章synchronized锁膨胀、锁升级、锁优化

等待唤醒与synchronized

notify()、notifyAll()和wait()这三个方法,就是实现多线程中的等待唤醒机制,使用这三个方法时,必须处于synchronized代码块或synchronized方法中,否则抛出异常,这是因为调用这几个方法前必须要拿到当前锁对象的监视器对象,也就是notify()、notifyAll()、wait()方法依赖于Monitor对象

注意:与sleep()方法不同的是,wait()方法调用完成后,线程会被暂停,线程将会释放当前持有的Monitor,直到其他线程调用notify()或notifyAll()方法后才能重新竞争锁;而sleep()方法只让线程休眠但不释放锁。

参考资料

深入理解Java并发之synchronized实现原理_synchronized原理_zejian_的博客-CSDN博客

Java并发编程:Synchronized及其实现原理 - liuxiaopeng - 博客园

☆啃碎并发(七):深入分析Synchronized原理 - 简书

JAVA系列教程:Object Monitor与Synchronized关键字_Mary Ling的博客-CSDN博客

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

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

相关文章

pycorrector一键式文本纠错工具,整合了BERT、MacBERT、ELECTRA、ERNIE等多种模型,让您立即享受纠错的便利和效果

pycorrector:一键式文本纠错工具,整合了Kenlm、ConvSeq2Seq、BERT、MacBERT、ELECTRA、ERNIE、Transformer、T5等多种模型,让您立即享受纠错的便利和效果 pycorrector: 中文文本纠错工具。支持中文音似、形似、语法错误纠正,pytho…

每天一道leetcode:300. 最长递增子序列(动态规划中等)

今日份题目: 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] …

【力扣每日一题】1572. 矩阵对角线元素的和 8.11打卡

文章目录 题目思路代码 题目 1572. 矩阵对角线元素的和 难度: 简单 描述: 给你一个正方形矩阵 mat,请你返回矩阵对角线元素的和。 请你返回在矩阵主对角线上的元素和副对角线上且不在主对角线上元素的和。 返回合并后的二叉树。 注意…

Python web实战之Django 的缓存机制详解

关键词:Python、Web 开发、Django、缓存 1. 缓存是什么?为什么需要缓存? 在 Web 开发中,缓存是一种用于存储数据的临时存储区域。它可以提高应用程序的性能和响应速度,减轻服务器的负载。 当用户访问网页时&#xff…

【金融量化】对企业进行估值的方法有哪些?

估值的方法有哪些? 如何对企业进行估值?有2个方法估算。 1 绝对估值法 它是一种定价模型,用于计算企业的内在价值。 比如说你可以根据公司近N年的现金流情况。借此去预测未来N年的现金流情况。所有的现金流数据都可以在年报上查询到。最后…

adb 通过wifi连接手机

adb 通过wifi连接手机 1. 电脑通过USB线连接手机2. 手机开启USB调试模式,开启手机开发者模式3.手机开启USB调试模式 更多设置-》开发者选项-》USB调试4.点击Wi-Fi 高级设置,可以查看到手机Wi-Fi的IP地址,此IP地址adb命令后面的ip地址&#xf…

【D3S】REST接口文档自动生成 - 集成smart-doc并同步配置到Torna

目录 一、引言二、maven插件三、smart-doc.json配置四、smart-doc-maven-plugin相关命令五、推送文档到Torna六、通过Maven Profile简化构建 一、引言 D3S(DDD with SpringBoot)为本作者使用DDD过程中开发的框架,目前已可公开查看源码&#…

TCP/IP 下的计算机网络江湖

〇、引言 在当今数字化时代,计算机网络宛如广袤江湖,涵盖着五大门派:物理层、数据链路层、网络层、传输层和应用层。每个门派独具技能,共同构筑着现代网络的框架。物理层宛如江湖基石,将比特流传输;数据链路层如武林传承,组织数据帧传递;网络层则像导航大师,寻找传送路…

js的练习

这里写目录标题 工具代码运行结果 工具 HBuilder X 代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script>window.onload function() // 需要在body加载完成之后&#xff0c;才能通过docu…

立秋至 | 共建智慧城,秋日硕果时

一缕缕阳光洒向大地 一股股热浪迎面拂来 一声声虫鸣清脆悦耳 一片片黄叶轻声而落 一份份清凉沁入心间 一个个硕果接踵而至 跟随我们一起来回顾下 往期铭控小伙伴们 在助力建设智慧城市 做了哪些努力呢 都做了哪些项目呢 得到了多少客户的认可呢 Part 1 智慧消防 消防…

LVS简介及LVS-DR搭建

目录 一. LVS简介&#xff1a; 1.简介 2. LVS工作模式&#xff1a; 3. LVS调度算法&#xff1a; 4. LVS-DR集群介绍&#xff1a; 二.LVS-DR搭建 1.RS配置 1&#xff09;两台RS&#xff0c;需要下载好httpd软件并准备好配置文件 2&#xff09;添加虚拟IP&#xff08;vip&…

算法通过村第三关-数组青铜笔记|单调数组

文章目录 前言单调数组问题搜索插入位置&#xff1a;数组合并问题&#xff1a;总结 前言 提示&#xff1a;本份真诚面对自己、坦然无碍面对他人&#xff0c;就是优雅。 数组中的比较经典性问题: 单调数组问题数组合并问题 单调数组问题 参考例子&#xff1a;896. 单调数列…