前言: 最近上操作系统课,老师在讲访问临界区(Critical Area)的方法,其实就多线程同时访问共享数据时保证不会出错的方法了,讲过采用信号量的方法之后,老师就讲了Monitor(管程)这个方法,就是采用Synchronized,我觉得还挺有意思的(况且从功利的角度看,面试容易出,嘿嘿嘿),遂记录与此,供以后复习用。
1. Synchronized是什么?要学习一个东西,我们肯定先要知道它是什么了,在Java中,Synchronized其实就是一个关键字,用来实现线程依次排队操作共享变量的功能,从而实现线程的互斥,再翻译一下就是用了Synchronized之后就可以给代码块上锁,谁拿到这个锁谁才能访问那个代码(如果拿到锁的是类,那么该类的所有对象是同一把锁)。虽然听起来就效率很低的样子,但是咱毕竟还不会啥别的(好吧,不是咱,是我),所以好好开始学习吧。
2. Synchronized的使用上文我们也说了,synchronized就是java里面的一个关键字,所以肯定是用来修饰java的代码块里了。具体有以下几个位置可用:
-
public synchronized void method() { .......}
1. 用来修饰方法,作用于实例方法,被锁为类的实例对象
-
public static synchronized void method1() { .......}
2. 用来修饰静态方法,作用于类对象,被锁的为类对象
-
synchronized (this) { .......}
3. 用来修饰代码块,作用于实例对象,被锁的为类的实例对象 this表示当前实例
-
synchronized (SynchronizedScopeDemo.class) { .......}
4. 被锁的为类对象
3. Synchronized的实现机制这里以重量级锁为例说明synchronized的实现机制,那为什么是重量级锁那,嘿嘿嘿,因为重量级锁为synchronized优化前的状态,而在JDK1.6之后对synchronized进行了优化,加入了偏向锁和轻量级锁。
要想理解重量级锁,不得不提到对象头的概念了,什么是对象头那? 对象头是JVM中,对象在内存中存储的布局区域之一(另外还有两个区域,实例数据:存放类的属性数据信息和填充数据和填充数据),对象头又被分成两部分:Mark Word(标记字段) & Class Pointer(类型指针)。下面我们将目光转回我们的重量级锁,在重量级锁中,对象头由指向重量级锁(Monitor对象)的指针和锁标志位组成。
由此可以观之: Monitor对象就是实现我们重量级锁的重点了,况且也是操作系统里我们学习的重点,已经迫不及待开始了那~
Monitor对象:Monitor对象被称之为管程,啦啦啦,就是操作系统里的那个管程呀!!!我觉得翻译的还不错,管程——顾名思义就是管理程序用的,它所做的也是管理我们的程序只能被得到锁的线程访问,而其他线程不能访问~~在Java之中,每个对象实例都会自动关联一个Monitor对象,这个Monitor对象与对象实例同生死,在线程视图获取对象锁时自动生成。当这个Monitor对象被线程持有时,它就被锁定了,其他线程就不能访问了。
让我们来看看Monitor在JVM的实现代码吧!
ObjectMonitor() {
_header = NULL;
_count = 0; //记录线程获取锁的个数,获取到锁后count加1,释放锁后count减1
_waiters = 0,
_recursions = 0; // 线程重入次数
_object = NULL;
_owner = NULL; //表示持有monitor对象的线程,初始值为NULL,表示没有线程持有,
_WaitSet = NULL; // 调用wait方法后的线程会被加入到_WaitSet队列中排着
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // 阻塞队列,线程被唤醒后(notify方法被调用)根据决策判读是放入cxq还是EntryList
FreeNext = NULL ;
_EntryList = NULL ; // 没有抢到锁的线程会被放到这个队列
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
某个线程获取到对象的monitor后,便将monitor中的owner设置为该线程的ID,同时将计数器count++。调用锁对象的wait方法后,线程就会释放当前持有的monitor,并将owner重置为空,count--,然后把该线程放进 _WaitSet队列中等待被再次宠幸。
然后那——这里面那三个队列存放的都是封装了线程的ObjectWaiter对象。略略略,具体的看代码上面的注释吧。大概就是,对已经得到锁的进程调用wait方法后,这个进程就去_WaitSet队列了,调用锁对象的notify方法后,根据情况的不同,判断这个进程是去_cxq还是去_EntryList。没抢到锁的进程就去_EntryList,获得锁的进程被释放之后,要么执行_EntryList中的进程,要么把_cxq中的进程转移到_EntryList然后再执行_EntryList中的进程。
那么synchronized是如何通过Monitor对象实现线程间的互斥的那?synchronized在同步代码块的入口处加入 monitorenter,同步代码块出口处加入moniterexit。
monitorenter执行时那,线程就会竭尽全力的获取该对象的锁,当该对象的moniter的count==0时,该线程就可成功获取这个锁了,并将count++。那如果偏偏是其他线程在拥有锁的话,那么对不起了,当前线程再努力也没用了,它只能乖乖去_EbtryList里和大家一起排队去了。直到等待的锁被释放(即执行到moniterexit指令结束),其他线程才能重新获得锁。
同步方法与同步代码块的实现类似,就是会在其中加一个ACC_SYNCHRONIZED标记位用来标记该方法是否是可获取锁,其实就是用标记位代替了monitorenter和moniterexit的使用。
重量级锁会导致程序高频的发生用户态和内核态的转变,严重影响程序性能。当然,JDK1.6之后对重量级锁做了改进。我们下次再一起学习吧,今天就先学到这里吧~~~
写的非常非常好的参考文章:
这一次,彻底搞懂Java中的synchronized关键字 - 掘金 (juejin.cn)https://juejin.cn/post/6973571891915128846



