- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
大家好,我是王有志。关注 王有志 ,一起聊技术,聊游戏,聊在外漂泊的生活.
今天我们继续学习 synchronized 的升级过程,目前只剩下最后一步了:轻量级锁->重量级锁.
通过今天的内容,希望能帮助大家解答 synchronized都问啥? 中除锁粗化,锁消除以及Java 8对 synchronized 的优化外全部的问题.
从源码揭秘偏向锁的升级 最后,看到 synchronizer#slow_enter 如果存在竞争,会调用 ObjectSynchronizer::inflate 方法,进行轻量级锁的升级(膨胀).
Tips :
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
......
ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD);
}
通过 ObjectSynchronizer::inflate 获取重量级锁ObjectMonitor,然后执行 ObjectMonitor::enter 方法.
Tips :
ObjectSynchronizer::inflate
,因此代码分析放在 重量级锁源码分析 中。 了解 ObjectMonitor::enter 的逻辑前,先来看 ObjectMonitor 的结构:
class ObjectMonitor {
private:
// 保存与ObjectMonitor关联Object的markOop
volatile markOop _header;
// 与ObjectMonitor关联的Object
void* volatile _object;
protected:
// ObjectMonitor的拥有者
void * volatile _owner;
// 递归计数
volatile intptr_t _recursions;
// 等待线程队列,cxq移入/Object.notify唤醒的线程
ObjectWaiter * volatile _EntryList;
private:
// 竞争队列
ObjectWaiter * volatile _cxq;
// ObjectMonitor的维护线程
Thread * volatile _Responsible;
protected:
// 线程挂起队列(调用Object.wait)
ObjectWaiter * volatile _WaitSet;
}
_header 字段存储了Object的markOop,为什么要这样?因为 锁升级后没有空间存储Object的markOop了,存储到_header中是为了在退出时能够恢复到加锁前的状态 .
Tips :
EntryList
中等待线程来自于 cxq
移入,或 Object.notify
唤醒但未执行。 objectMonito#enter 方法可以拆成三个部分,首先是 竞争成功或重入的场景 :
// 获取当前线程Self
Thread * const Self = THREAD;
// CAS抢占锁,如果失败则返回_owner
void * cur = Atomic::cmpxchg(Self, &_owner, (void*)NULL);
if (cur == NULL) {
// CAS抢占锁成功直接返回
return;
}
// CAS失败场景
// 重量级锁重入
if (cur == Self) {
// 递归计数+1
_recursions++;
return;
}
// 当前线程是否曾持有轻量级锁
// 可以看做是特殊的重入
if (Self->is_lock_owned ((address)cur)) {
// 递归计数器置为1
_recursions = 1;
_owner = Self;
return;
}
重入和升级的场景中,都会操作 _recursions 。 _recursions 记录了进入ObjectMonitor的次数,解锁时要经历相应次数的退出操作才能完成解锁.
以上都是成功获取锁的场景,那么产生竞争导致失败的场景是怎样的呢?来看 适应性自旋 的部分, ObjectMonitor倒数第二次对“轻量”的追求 :
// 尝试自旋来竞争锁
Self->_Stalled = intptr_t(this);
if (Knob_SpinEarly && TrySpin (Self) > 0) {
Self->_Stalled = 0;
return;
}
objectMonitor#TrySpin 方法是对 适应性自旋 的支持。Java 1.6后加入,移除默认次数的自旋,将自旋次数的决定权交给JVM.
JVM根据锁上一次自旋情况决定,如果刚刚自旋成功,并且持有锁的线程正在执行,JVM会允许再次尝试自旋。如果该锁的自旋经常失败,那么JVM会直接跳过自旋过程 .
Tips :
到目前为止,无论是CAS还是自旋,都是偏向锁和轻量级锁中出现过的技术,为什么会让ObjectMonitor背上“重量级”的名声呢?
最后是竞争失败的场景:
// 此处省略了修改当前线程状态的代码
for (;;) {
EnterI(THREAD);
}
实际上,进入 ObjectMonitor#EnterI 后也是先尝试“轻量级”的加锁方式:
void ObjectMonitor::EnterI(TRAPS) {
if (TryLock (Self) > 0) {
return;
}
if (TrySpin (Self) > 0) {
return;
}
}
接来下是重量级的真正实现:
// 将当前线程(Self)封装为ObjectWaiter的node
ObjectWaiter node(Self);
Self->_ParkEvent->reset();
node._prev = (ObjectWaiter *) 0xBAD;
node.TState = ObjectWaiter::TS_CXQ;
// 将node插入到cxq的头部
ObjectWaiter * nxt;
for (;;) {
node._next = nxt = _cxq;
if (Atomic::cmpxchg(&node, &_cxq, nxt) == nxt)
break;
// 为了减少插入到cxq头部的次数,试试能否直接获取到锁
if (TryLock (Self) > 0) {
return;
}
}
逻辑一目了然,封装 ObjectWaiter 对象,并加入到 cxq 队列头部。接着往下执行:
// 将当前线程(Self)设置为当前ObjectMonitor的维护线程(_Responsible)
// SyncFlags的默认值为0,可以通过-XX:SyncFlags设置
if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
Atomic::replace_if_null(Self, &_Responsible);
}
for (;;) {
// 尝试设置_Responsible
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::replace_if_null(Self, &_Responsible);
}
// park当前线程
if (_Responsible == Self || (SyncFlags & 1)) {
Self->_ParkEvent->park((jlong) recheckInterval);
// 简单的退避算法,recheckInterval从1ms开始
recheckInterval *= 8;
if (recheckInterval > MAX_RECHECK_INTERVAL) {
recheckInterval = MAX_RECHECK_INTERVAL;
}
} else {
Self->_ParkEvent->park();
}
// 尝试获取锁
if (TryLock(Self) > 0)
break;
if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0)
break;
if (_succ == Self)
_succ = NULL;
}
逻辑也不复杂,不断的 park 当前线程,被唤醒后尝试获取锁。需要关注 -XX:SyncFlags 的设置:
SyncFlags == 0
时, synchronized
直接挂起线程; SyncFlags == 1
时, synchronized
将线程挂起指定时间。 前者是永久挂起,需要被其它线程唤醒,而后者挂起指定的时间后自动唤醒 .
Tips : 关于线程你必须知道的8个问题(中) 聊到过 park 和 parkEvent ,底层是通过 pthread_cond_wait 和 pthread_cond_timedwait 实现的.
释放重量级锁的源码和注释非常长,我们省略大部分内容,只看关键部分.
我们知道,重入是不断增加 _recursions 的计数,那么退出重入的场景就非常简单了:
void ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * const Self = THREAD;
// 第二次持有锁时,_recursions == 1
// 重入场景只需要退出重入即可
if (_recursions != 0) {
_recursions--;
return;
}
.....
}
不断的减少 _recursions 的计数.
JVM的实现中,当前线程是锁的持有者且没有重入时, 首先会释放自己持有的锁,接着将改动写入到内存中,最后还肩负着唤醒下一个线程的责任 。先来看释放和写入内存的逻辑:
// 置空锁的持有者
OrderAccess::release_store(&_owner, (void*)NULL);
// storeload屏障,
OrderAccess::storeload();
// 没有竞争线程则直接退出
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
TEVENT(Inflated exit - simple egress);
return;
}
storeload 屏障,对于如下语句:
store1;
storeLoad;
load2
保证 store1 指令的写入在 load2 指令执行前,对所有处理器可见.
Tips : volatile 中详细解释内存屏障.
执行释放锁和写入内存后,只需要唤醒下一个线程来“交接”锁的使用权。但是有两个“等待队列”: cxq 和 EntryList ,该从哪个开始唤醒呢?
Java 11前,根据 QMode 来选择不同的策略:
QMode == 0
,默认策略,将 cxq
放入 EntryList
; QMode == 1
,翻转 cxq
,并放入 EntryList
; QMode == 2
,直接从 cxq
中唤醒; QMode == 3
,将 cxq
移入到 EntryList
的尾部; QMode == 4
,将 cxq
移入到 EntryList
的头部。 不同的策略导致了不同的唤醒顺序,现在你知道为什么说 synchronized 是非公平锁了吧?
objectMonitor#ExitEpilog 方法就很简单了,调用的是与 park 对应的 unpark 方法,这里就不多说了.
Tips : Java 12的objectMonitor 移除了 QMode ,也就是说只有一种唤醒策略了.
我们对重量级锁做个总结。 synchronized 的重量级锁是 ObjectMonitor ,它使用到的关键技术有 CAS和park 。相较于 mutex#Monitor 来说,它们的本质相同,对park的封装,但 ObjectMonitor 是做了大量优化的复杂实现.
我们看到了重量级锁是如何实现重入性的,以及唤醒策略导致的“不公平”。那么我们常说的 synchronized 保证了原子性,有序性和可见性,是如何实现的呢?
大家可以先思考下这个问题,下篇文章会做一个全方位的总结,给 synchronized 收下尾.
好了,今天就到这里了,Bye~~ 。
最后此篇关于09.什么是synchronized的重量级锁?的文章就讲到这里了,如果你想了解更多关于09.什么是synchronized的重量级锁?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
一、公平锁和非公平锁 1.1、公平锁和非公平锁的概述 公平锁:指多个线程按照申请锁的顺序来获取锁。 非公平锁:指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁
阅读目录 1、简介 2、分类 3、全局锁 4、表级锁 5、表锁 6、元数据锁
因此,在我编写的程序中,我有三个函数,为了简单起见,我们将它们称为 A、B 和 C。每个函数都需要访问资源X才能工作。 限制是A和B不允许同时运行并且必须适当同步。但是,C 可以与 A 或 B 同时运
我听说过这些与并发编程相关的词,但是锁、互斥量和信号量之间有什么区别? 最佳答案 锁只允许一个线程进入被锁定的部分,并且该锁不与任何其他进程共享。 互斥锁与锁相同,但它可以是系统范围的(由多个进程共享
这个问题已经有答案了: What is an efficient way to implement a singleton pattern in Java? [closed] (29 个回答) 已关闭
这个问题已经有答案了: What is an efficient way to implement a singleton pattern in Java? [closed] (29 个回答) 已关闭
我对标题中的主题有几个问题。首先,假设我们使用 JDBC,并且有 2 个事务 T1 和 T2。在 T1 中,我们在一个特定的行上执行 select 语句。然后我们对该行执行更新。在事务 T2 中,我们
我希望我的函数只运行一次。这意味着如果多个线程同时调用它,该函数将阻塞所有线程,只允许它运行。 最佳答案 听起来您希望存储过程进行同步。为什么不直接将同步放在应用程序本身中。 pthread_mute
if (runInDemoMode) { lock (this) { //Initalization of tables dCreator.create
我相信无论使用什么语言都可以考虑我的问题,但是为了有一些“ anchor ”,我将使用 Java 语言来描述它。 让我们考虑以下场景:我有一个扩展 Thread 的类 PickyHost 及其实例 p
我知道异步不是并行的,但我现在遇到了一个非常有趣的情况。 async function magic(){ /* some processing here */ await async () =
我们正在使用 Scala、Play 框架和 MongoDB(以 ReactiveMongo 作为我们的驱动程序)构建一个网络应用程序。应用程序架构是端到端的非阻塞。 在我们代码的某些部分,我们需要访问
我需要一个简单的锁,JavaME 超时(concurrent.lock 的反向移植需要完整的 Java 1.3)。 如果其他人已经为 JavaME 发布了经过测试的锁定代码,我宁愿使用它。 锁定是出了
根据 boost : To access the object, a weak_ptr can be converted to a shared_ptr using the shared_ptr co
关于 Mutex 和 Critical 部分之间的区别存在一个问题,但它也不处理 Locks。 所以我想知道临界区是否可以用于进程之间的线程同步。 还有信号状态和非信号状态的含义 最佳答案 在 Win
锁 最为常见的应用就是 高并发的情况下,库存的控制。本次只做简单的单机锁介绍。 直接看代码: 每请求一次库存-1. 假如库存1000,在1000个人请求之后,库存将变为0。
线程和进程 1、线程共享创建它的进程的地址空间,进程有自己的地址空间 2、线程可以访问进程所有的数据,线程可以相互访问 3、线程之间的数据是独立的 4、子进程复制线程的数据 5、子进程启动
**摘要:**细心的你也一定关注到,有的网址是https开头的,有的是http。https开头的网站前面,会有一把小锁。这是为什么呢? 本文分享自华为云社区《还不知道SSL证书已经是刚需了?赶快来了解
试图在 C 中实现一个非常简单的互斥锁(锁)我有点困惑。我知道互斥锁类似于二进制信号量,除了互斥锁还强制执行释放锁的线程的约束,必须是最近获得它的同一线程。我对如何跟踪所有权感到困惑? 这是我到目前为
在阅读了很多与上述主题相关的文章和答案之后,我仍然想知道 SQL Server 数据库引擎在以下示例中是如何工作的: 假设我们有一个名为 t3 的表: create table t3 (a int ,
我是一名优秀的程序员,十分优秀!