相关链接:

《synchronized锁住的是谁?》

我们知道synchronized是重量级锁,我们知道synchronized锁住的是一个对象上的Monitor对象,我们也知道synchronized用于同步代码块时会执行monitorenter和monitorexit等。

上面几个问题仅仅是校招级。

那么synchronized为什么“重”呢?Monitor对象从何而来呢?synchronized用于实例方法或者静态方法又是怎么锁住的呢?

《synchronized锁住的是谁?》中我们明确了,synchronized锁住的对象,本文讲述synchronized凭什么锁得住。

首先我们需要知道的是在Hotspot虚拟机实现中,对象实例在堆内存中结构分为3个部分:对象头、实例数据、对其填充字节。在Java中万物皆为对象。就算一个Java类被编译称为class二进制文件在被加载到内存时,它仍然会在堆内存中创建一个Class对象。这也就解释了,为什么synchronized能对类加锁(因为每个类在堆内存中有一个Class对象,对于类synchronized锁的实际上是Class对象,下文会继续解释)。

在解释了Java中对象实例在Hotspot中的内存结构(对象头、实例数据、对其填充字节)后,synchronized锁住的Monitor对象就存在于对象头之中。对象头又分为:Mark Word、指向类的指针、数组长度(数组对象)。

对象头在Hotspot虚拟机实现中,分为32位和64位的实现,实际上Hotspot源代码实现中的注释已经解释得非常清楚了(openjdk/hotspot/share/oops/markOop.hpp),对象头的Mark Word位格式在32位机器中是32位长,在64位机器中是64位长(采用 big endian ,低地址存放最高有效字节,即低位在左,高位再右)。

32bit位虚拟机Mark Word

锁状态

25bit

4bit

1bit

2bit

23bit

2bit

是否是偏向锁

锁标志位

无锁状态

对象的hashcode

分代年龄

0

01

偏向锁

线程ID

偏向时间戳

分代年龄

1

01

轻量级锁

指向栈中锁记录的指针

00

重量级锁

指向重量级锁(Monitor)的指针

10

GC标记

11

和synchronized相关的就是Java在Hotspot虚拟机实现中对象头中的Mark Word。

在以前(JDK5之前),synchronized被称为重量级锁是无可厚非的,但在JDK6后,JVM对其进行了一系列优化,尽量使得synchronized不再那么重。之所以synchronized重,是因为它涉及到了操作系统用户态与核心态的转换,下文再详细解释。这里我们从最轻的偏向锁->轻量级锁->重量级锁的过程,注意他们只能升级加锁的强度,不能降级。

偏向锁

上面提到了JDK6过后优化了synchronized的加锁过程,尽量使得synchronized不再那么重。偏向锁即是如此。

JVM的研究者表明,大多数情况下锁的竞争不是那么激励,在不那么激励的时候如果通过获取Monitor来进行同步访问,会造成线程在操作系统用户态和核心态的转换,这会使得系统性能下降。偏向锁表示,当只有一个线程进入同步方法或同步代码块时,并不会直接获取Monitor锁,而是先判断对象头中Mark Word部分的锁标志位是否处于“01”,如果处于“01”,此时再判断线程ID是否是本线程ID,如果是则直接进入方法进行后续操作;如果不是,此时则通过CAS(无锁机制竞争)如果竞争成功,此时将线程ID设置为本线程ID,如果竞争失败,说明造成了有了较为强烈的锁竞争,偏向锁已不能满足,此时偏向锁晋级为轻量级锁。

轻量级锁

当锁发生竞争时,持有偏向锁的线程会撤销偏向锁,转而晋级为轻量级锁(状态)。轻量级锁的核心是,不让未获取锁的线程进入阻塞状态,因为这会使得线程由用户态转为核心态,这会造成很大的性能损失,而是采用“死循环”的方式不断的获取锁,这种采用“死循环”获取的锁的方式称为——锁自旋。它不会让线程陷入阻塞,但同时仅适用于持有锁时间较短的场景。那么轻量级锁升级为重量级锁的条件就是,自旋等待的时间过长,并且又有了新的线程来竞争。

重量级锁

这种锁,就是地地道道原原本本synchronized的本意了。线程会去抢夺对象上的一个互斥量(这个互斥量就是Monitor),每个对象都会有,就算是类也有一个Monitor互斥量(因为类在堆内存中有一个Class对象)。当一个线程获取到对象的Monitor锁时,其余线程会被阻塞挂起,并且由用户态转为核心态。

上文提到在锁的竞争状态晋级为重量级锁时,Java对象头中的Mark Word前30位存储的是Monitor对象的指针。Monitor对象定义在openjdk/hotspot/share/runtime/objectMonitor.hpp中,在ObjectMonitor中定义了:计数器、持有Monitor的线程、处于wait状态的线程、处于阻塞状态的线程等等。

synchronized无论是普通实例还是同步代码块,它所获取的锁是对象实例中的Monitor锁,而对象的Monitor又是存在于Java对象头的Mark Work之中,所以可以这么说,synchronized获取的锁在Java对象头中。对于普通实例或者静态方法,JVM并没有显示的指令进入临界区,而是在方法上标识了“ACC_SYNCHRONIZED”,标识是synchronized同步方法,方法内部都是临界区。而对于同步代码块,则在synchronized代码块开始执行了monitorenter,结束或者抛出异常时执行了monitorexit指令。

synchronized凭借的就是Monitor锁住的对象,Monitor又是借助于操作系统的mutex lock,之所以它重是因为它被挂起后线程会由用户态转换为内核态,这个转换会带来性能损耗。JDK6开始对其进行了优化,提出了偏向锁和轻量级锁,针对锁竞争较为激烈的场景不会直接去获取Monitor对象,减少性能损耗。因此在现如今的synchronized实现中,它的性能劣势也已不再那么明显。

这是一个能给程序员加buff的公众号 (CoderBuff)

synchronized凭什么锁得住?的更多相关文章

  1. synchronized 作为悲观锁,锁住了什么?

    继续来认识 synchronized,上篇文章加不加 synchronized 有什么区别?我们了解了 synchronized 是在多线程并发竞争同一资源的时候使用,这一篇我们来了解,synchro ...

  2. (转)Synchronized(对象锁)和Static Synchronized(类锁)的区别

    场景:面试的时候经常用得到! 1 综述 Synchronized和Static Synchronized区别 一个是实例锁(锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念),一个是全 ...

  3. 精通java并发-synchronized关键字和锁

    目前CSDN,博客园,简书同步发表中,更多精彩欢迎访问我的gitee pages synchronized关键字和锁 示例代码 public class MyThreadTest2 { public ...

  4. synchronized是对象锁还是全局锁

    昆昆欧粑粑 2019-02-20 15:09:59 1148 收藏 1分类专栏: java学习 文章标签: synchronized 全局锁 对象锁 同步版权都可以锁!synchronized(thi ...

  5. synchronized内置锁

    synchronized内置锁,如果发生阻塞,无法被中断,除非关闭jvm.因此不能从死锁中恢复.

  6. Java 线程锁机制 -Synchronized Lock 互斥锁 读写锁

    (1)synchronized 是互斥锁: (2)ReentrantLock 顾名思义 :可重入锁 (3)ReadWriteLock :读写锁 读写锁特点: a)多个读者可以同时进行读b)写者必须互斥 ...

  7. Java多线程简析——Synchronized(同步锁)、Lock以及线程池

    Java多线程 Java中,可运行的程序都是有一个或多个进程组成.进程则是由多个线程组成的.最简单的一个进程,会包括mian线程以及GC线程. 线程的状态 线程状态由以下一张网上图片来说明: 在图中, ...

  8. Java多线程系列 基础篇06 synchronized(同步锁)

    转载 http://www.cnblogs.com/paddix/ 作者:liuxiaopeng http://www.infoq.com/cn/articles/java-se-16-synchro ...

  9. synchronized:内部锁

    synchronized:内部锁 起源: 并行程序开发涉及多线程.多任务间的协作和数据共享 一).内部锁:synchronized 1).定义在方法上 public synchronized void ...

随机推荐

  1. SQL语句中将形式(12/10/19 4:13:21 PM)转化为(2019-12-10)

    convert(char(10),convert(datetime, H.TOEX_FIN_REJECT_TIMR)+1,20),

  2. Python的Requests库基本方法函数

    一.Requests 库的七个常用函数: 1. requests.request(method,url,**kwargs) :method:请求方式,对应get/put/post等七种 :拟获取页面的 ...

  3. Use Swift Dynamic Framework (如何科学地引用第三方 Swift 库)

    转自:http://andelf.github.io/blog/2014/07/07/use-swift-dynamic-library/ CocoaPods 由于完全使用静态链接解决方法,过度依赖 ...

  4. node.js调试入门

    1-1 Inspector介绍 使用Inspector调试Node.js的优势 可查看当前上下文的变量 可观察当前函数调用堆栈 不侵入代码 可在暂停状态下执行指定代码 Inspector的构成以及原理 ...

  5. hdu3791二叉搜索树

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3791 题意:给定一个n(多组,n为0时结束),给一个串和n个串,分别判断n个串按序列构建的二叉搜索树和 ...

  6. 【Nodejs】392- 基于阿里云的 Node.js 稳定性实践

    前言 如果你看过 2018 Node.js 的用户报告,你会发现 Node.js 的使用有了进一步的增长,同时也出现了一些新的趋势. Node.js 的开发者更多的开始使用容器并积极的拥抱 Serve ...

  7. BIOS安全设置

    1.开机按F2进入BIOS 2.进入 Security 界面 3.Set user password 用户密码 开机密码 设置为123456 4.Set supervisor password 进BI ...

  8. CentOS 7.4搭建LAMP,LAMP:Linux、Apache、MySQL、PHP

    CentOS 7.4搭建LAMP,LAMP:Linux.Apache.MySQL.PHP. 目录: 第一部分 准备工作 第二部分 安装Apache服务 第三部分 安装MySQL服务 第四部分 搭建PH ...

  9. cannot resolve symbol AppCompatActivity

    记一次配置性的问题 今天复习自定义控件的时候新建一个project,生成的代码冒红,可把我给郁闷了.我知道我的配置是正确的,可是... 它出现了cannot resolve symbol AppCom ...

  10. WebGPU学习(六):学习“rotatingCube”示例

    大家好,本文学习Chrome->webgpu-samplers->rotatingCube示例. 上一篇博文: WebGPU学习(五): 现代图形API技术要点和WebGPU支持情况调研 ...