java thread monitor_java并发系列-monitor机制实现

背景

在jdk1.6以前synchronized的java内置锁不存在 偏向锁->轻量级锁->重量级锁 的锁膨胀机制,锁膨胀机制是1.6之后为了优化java线程同步性能而实现的。而1.6之前都是基于monitor机制的重量级锁。因为java内部对锁实现的封装,就算现在我们也只需要了解重量级锁就可以了。深入了解monitor机制对学习线程同步非常重要。

正文

目录

什么是monitor

monitor的作用

monitor的组成

寻找monitor锁

java monitor机制的实现

什么是monitor 参考

monitor直译过来是监视器的意思,专业一点叫管程。monitor是属于编程语言级别的,它的出现是为了解决操作系统级别关于线程同步原语的使用复杂性,类似于语法糖,对复杂操作进行封装。而java则基于monitor机制实现了它自己的线程同步机制,就是synchronized内置锁。

monitor的作用

monitor的作用就是限制同一时刻,只有一个线程能进入monitor框定的临界区,达到线程互斥,保护临界区中临界资源的安全,这称为线程同步使得程序线程安全。同时作为同步工具,它也提供了管理进程,线程状态的机制,比如monitor能管理因为线程竞争未能第一时间进入临界区的其他线程,并提供适时唤醒的功能。

monitor的组成

3.1 monitor对象

monitor对象是monitor机制的核心,它本质上是jvm用c语言定义的一个数据类型。对应的数据结构保存了线程同步所需的信息,比如保存了被阻塞的线程的列表,还维护了一个基于mutex的锁,monitor的线程互斥就是通过mutex互斥锁实现的。

3.2 临界区

临界区是被synchronized包裹的代码块,可能是个代码块,也可能是个方法。

3.3 条件变量

条件变量和下方wait signal方法的使用有密切关系 。在获取锁进入临界区之后,如果发现条件变量不满足使用wait方法使线程阻塞,条件变量满足后signal唤醒被阻塞线程。 tips:当线程被signal唤醒之后,不是从wait那继续执行的,而是重新while循环一次判断条件是否成立。参考

3.4 定义在monitor对象上的wait() signal() signalAll()操作

java中monitor的实现

4.1 首先先看一下synchronized同步代码块和同步方法编译后的字节码指令文件分别是什么样子

源代码如下

public classSynchronizedTest {public synchronized voidtest1(){

}public voidtest2(){synchronized (this){

}

}

}

接着我们用javap查看

6f21a087c7601cc28e4e1bb7b62d0248.png

efddaf59d12eaccff1c8f3f9bd738c1d.png

从上面可以看出,同步方法jvm是使用ACC_SYNCHRONIZED方法访问标识符实现同步,同步代码块jvm是使用monitorenter和monitorexit指令包裹临界区实现同步。

4.2 线程执行到同步方法处和同步代码块monitorenter和monitorexit指令分别发生了什么

这里需要看jvm的官方文档,下面三段话要好好读一读,monitor的运行逻辑都包含在里面。

同步方法 文档

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

2.11.10. Synchronization

The Java Virtual Machine supports synchronization of both methods and sequences of instructions within a method by a single synchronization construct: the monitor.

Method-level synchronization is performed implicitly, as part of method invocation and return (§2.11.8). A synchronized method is distinguished in the run-time constant pool's method_info structure (§4.6) by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.

Synchronization of sequences of instructions is typically used to encode thesynchronized block of the Java programming language. The Java Virtual Machine supplies the monitorenter and monitorexit instructions to support such language constructs. Proper implementation of synchronized blocks requires cooperation from a compiler targeting the Java Virtual Machine (§3.14).

Structured locking is the situation when, during a method invocation, every exit on a given monitor matches a preceding entry on that monitor. Since there is no assurance that all code submitted to the Java Virtual Machine will perform structured locking, implementations of the Java Virtual Machine are permitted but not required to enforce both of the following two rules guaranteeing structured locking. Let T be a thread and M be a monitor. Then:

The number of monitor entries performed by T on M during a method invocation must equal the number of monitor exits performed by T on M during the method invocation whether the method invocation completes normally or abruptly.

At no point during a method invocation may the number of monitor exits performed by T on M since the method invocation exceed the number of monitor entries performed by T on M since the method invocation.

Note that the monitor entry and exit automatically performed by the Java Virtual Machine when invoking asynchronized method are considered to occur during the calling method's invocation.

2.11.10。同步化

Java虚拟机通过单个同步结构(监视器)支持方法和方法中指令序列的同步。

作为方法调用和返回的一部分,方法级同步是隐式执行的(第2.11.8节)。甲synchronized方法是在运行时间常量池中的区分method_info结构(§4.6由)ACC_SYNCHRONIZED标志,这是由方法调用指令进行检查。调用方法时ACC_SYNCHRONIZED设置为1时,无论方法调用是正常完成还是突然完成,执行线程都将进入监视器,调用方法本身并退出监视器。在执行线程拥有监视器的时间内,没有其他线程可以进入它。如果在调用synchronized方法期间引发了异常并且该synchronized方法不处理该异常,则在将该异常重新抛出该方法之前,该方法的监视器将自动退出synchronized。

指令序列的同步通常用于对synchronizedJava编程语言的块进行编码 。Java虚拟机提供了 monitorenter和monitorexit指令来支持这种语言构造。正确实现synchronized块需要目标Java虚拟机(第3.14节)的编译器的配合。

当方法调用期间,给定监视器上的每个出口与该监视器上的先前条目匹配时,就是结构锁定。由于不能保证提交给Java虚拟机的所有代码都将执行结构化锁定,因此允许但不要求强制执行以下两个保证结构化锁定的规则的Java虚拟机实现。设 T为线程, M为监视器。然后:

进行监控条目的数量由Ť上中号的方法调用期间必须等于由执行监控退出的数目Ť上中号 是否该方法调用完成正常或突然的方法调用期间。

在一个方法调用期间没有点可以通过执行监控退出的数目Ť 上中号,因为该方法的调用超过执行监视器条目的数量Ť 上中号,因为该方法调用。

请注意,在调用synchronized方法时,Java虚拟机在调用方法时自动执行的监视器进入和退出 被视为发生。

View Code

同步代码块指令 文档

monitorenter

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

The objectref must be of type reference.

Each object is associated with a monitor. A monitor is lockedif and only ifit has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

该objectref的类型必须是reference。

每个对象都与一个监视器关联。监视器只有在拥有所有者的情况下才被锁定。执行monitorenter的线程 尝试获得与objectref关联的监视器的所有权,如下所示:

如果与objectref关联的监视器的条目计数 为零,则线程进入监视器,并将其条目计数设置为1。然后,该线程是监视器的所有者。

如果线程已经拥有与objectref关联的监视器 ,则它将重新进入监视器,从而增加其条目计数。

如果另一个线程已经拥有与objectref相关联的监视器 ,则该线程将阻塞,直到该监视器的条目计数为零为止,然后再次尝试获取所有权。

View Code

monitorexit

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to doso.

该线程减少与objectref关联的监视器的条目计数。结果,如果条目计数的值为零,则线程退出监视器,并且不再是其所有者。其他被阻止进入监视器的线程也可以尝试这样做。

View Code

对比官方文档描述的同步方法和同步代码块指令,其实功能类似。总结如下

1.同步方法和同步代码块都是通过monitor锁实现的。

2.两者的区别:同步方式是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现;同步代码块是通过monitorenter和monitorexit指令来实现

3.每个java对象都会与一个monitor相关联,可以由线程获取和释放。

4.如果线程没有获取到monitor会被阻塞。

5.monitor通过维护一个计数器来记录锁的获取,重入,释放情况。

由此可知当线程执行到同步方法发现此方法有ACC_SYNCHRONIZED标志或者执行到monitorenter指令时,会去尝试获取monitor锁。

那么就会有个疑问,既然线程需要获取monitor锁,那么什么是monitor锁,并且怎么才算获取monitor锁。

4.3 寻找monitor锁

这里先不甩结论,接下来我们一步一步搜寻monitor锁。

之前使用synchronized的时候知道,java中的每个对象都可以作为锁。

普通同步方法,锁是当前实例对象。

静态同步方法,锁是当前类的class对象。

同步代码块,锁是括号中的对象。

上面的官方文档也说了每个对象都与一个监视器关联。有理由猜测,任意的java对象在实例化的时候都同时生成了一个monitor锁与之一一对应。那么进一步猜测,通过java对象可以获取到和它对应的监视器。

这时候涉及到对象头的知识点。

4.3.1 对象头

|-------------------------------------------------------|--------------------|

| Mark Word (32 bits) | State |

|-------------------------------------------------------|--------------------|

| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |

|-------------------------------------------------------|--------------------|

| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |

|-------------------------------------------------------|--------------------|

| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |

|-------------------------------------------------------|--------------------|

| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |

|-------------------------------------------------------|--------------------|

| | lock:2 | Marked for GC |

|-------------------------------------------------------|--------------------|

每个java对象在内存中由对象头,实例数据和对齐填充三块区域组成。其中对象头存储了一些增强对象功能的信息,对象头中的Mark word 记录了锁的相关信息。如果此刻该对象锁升级为重量级锁,那么其中在对象头中存储了指向基于monitor锁的指针ptr_to_heavyweight_monitor。这个指针指向的就是我们苦苦寻找的锁。

既然监视器是指针指向的内存区域,那么这块内存区域肯定有自己的数据结构,而这个数据结构保存着线程同步的所有信息。

4.3.2 揭开monitor锁神秘面纱

详情参考

monitor的定义和初始化是有c语言编写的。

最重要的就是这两个c语言定义的类,objectMonitor就是对象头中指向的monitor重量级锁,objectWaiter是对等待线程的封装,可以用双向链表保存起来。

下面解释objectMonitor中属性的含义

_header

定义:

volatile markOop _header; // displaced object header word - mark

说明:

_header是一个markOop类型,markOop就是对象头中的Mark Word

_count

定义:

volatile intptr_t _count; // reference count to prevent reclaimation/deflation

// at stop-the-world time. See deflate_idle_monitors().

// _count is approximately |_WaitSet| + |_EntryList|说明:抢占该锁的线程数 约等于 WaitSet.size + EntryList.size

_waiters

定义:

volatile intptr_t _waiters; // number of waiting threads

说明:等待线程数

_recursions

定义:

volatile intptr_t _recursions; // recursion count, 0 for first entry

说明:锁重入次数

_object

定义:

void* volatile _object; // backward object pointer - strong root

说明:监视器锁寄生的对象。锁不是平白出现的,而是寄托存储于对象中

_owner

定义:

void * volatile _owner; // pointer to owning thread OR BasicLock

说明:

指向获得ObjectMonitor对象的线程或基础锁

_WaitSet

定义:

ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor

说明:处于wait状态的线程,被加入到这个linkedList

_WaitSetLock

定义:

volatile int _WaitSetLock; // protects Wait Queue - simple spinlock

说明:protects Wait Queue - simple spinlock ,保护WaitSet的一个自旋锁(monitor大锁里面的一个小锁,这个小锁用来保护_WaitSet更改)

_Responsible

定义:

Thread * volatile _Responsible

_succ

定义:

Thread * volatile _succ ; // Heir presumptive thread - used for futile wakeup throttling

说明:当锁被前一个线程释放,会指定一个假定继承者线程,但是它不一定最终获得锁。参考:https://www.jianshu.com/p/09de11d71ef8

_cxq

定义:

ObjectWaiter * volatile _cxq ; // LL of recently-arrived threads blocked on entry.

// The list is actually composed of WaitNodes, acting

// as proxies for Threads.

FreeNext

定义:

ObjectMonitor * FreeNext ; // Free list linkage

说明:未知

_EntryList

定义:

ObjectWaiter * volatile _EntryList ; // Threads blocked on entry or reentry.

说明:未获取锁被阻塞或者被wait的线程重新进入被放入entryList中

_SpinFreq

定义:

volatile int _SpinFreq ; // Spin 1-out-of-N attempts: success rate

说明:未知 可能是获取锁的成功率

_SpinClock

定义:

volatile int _SpinClock ;

说明:未知

OwnerIsThread

定义:

int OwnerIsThread ; // _owner is (Thread *) vs SP/BasicLock

说明:当前owner是thread还是BasicLock

_previous_owner_tid

定义:

volatile jlong _previous_owner_tid; // thread id of the previous owner of the monitor

说明:当前owner的线程id

其实上面的属性中我们真正需要了解的就几个。下面大概描述一下。

4.3.3 线程的千里追踪

42bd490d8d61de6440ba303de7bfcdb8.png

5ef051252871f2cab33258adad280aa5.png

线程访问同步代码,需要获取monitor锁

线程被jvm托管

jvm获取充当临界区锁的java对象

根据java对象对象头中的重量级锁 ptr_to_heavyweight_monitor指针找到objectMonitor

将当前线程包装成一个ObjectWaiter对象

将ObjectWaiter假如_cxq(ContentionList)队列头部

_count++

如果owner是其他线程说明当前monitor被占据,则当前线程阻塞。如果没有被其他线程占据,则将owner设置为当前线程,将线程从等待队列中删除,count--。

当前线程获取monitor锁,如果条件变量不满足,则将线程放入WaitSet中。当条件满足之后被唤醒,把线程从WaitSet转移到EntrySet中。

当前线程临界区执行完毕

Owner线程会在unlock时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)。Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交个OnDeck,OnDeck需要重新竞争锁

大概流程就是这样的,但是其中还有很多没有在这篇博客中提及的知识点就不深入了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C#中,Monitor类提供了一种同步机制,用于管理共享资源的并发访问。它可以用于实现互斥访问,即一次只允许一个线程访问共享资源。 要使用Monitor类,首先需要创建一个对象作为锁定对象。然后,可以使用Monitor类的静态方法来对该对象进行锁定和解锁操作。 下面是一个简单的示例,演示了如何使用Monitor来保护共享资源的访问: ```csharp class Program { static object lockObj = new object(); static int count = 0; static void Main(string[] args) { // 创建多个线程来访问共享资源 for (int i = 0; i < 5; i++) { Thread t = new Thread(IncrementCount); t.Start(); } Console.ReadLine(); } static void IncrementCount() { // 对lockObj进行锁定 Monitor.Enter(lockObj); try { // 访问共享资源 count++; Console.WriteLine("Count: " + count); } finally { // 解锁lockObj Monitor.Exit(lockObj); } } } ``` 在上面的示例中,我们创建了一个共享资源count和一个锁定对象lockObj。在IncrementCount方法中,我们使用Monitor.Enter方法对lockObj进行锁定,确保只有一个线程可以进入临界区。然后,在访问共享资源之后,我们使用Monitor.Exit方法解锁lockObj,以便其他线程可以继续进入临界区。 请注意,在使用Monitor时,应始终在try/finally块中进行锁定和解锁操作,以确保即使在发生异常时也能正确释放锁定。 这只是Monitor类的基本用法示例,你可以根据具体需求进行更复杂的同步操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值