Hi~朋友,点点关注不迷路
有人乐观,必有人悲观。锁的世界也不例外,乐观锁和悲观锁的悲欢各不相同。
摘要
1. 什么是悲观锁
悲观锁和乐观锁完全不同,悲观锁是实实在在对代码块进行加锁,被锁住的代码块,同一时刻只允许一个或几个线程同时进入,避免了多线程写坏共享数据问题。
2. 悲观锁的实现
Java中的悲观锁主要有以下几个实现:
3. Monitor Object模式
Monitor Object模式是为了解决线程安全问题,将多线程并发访问的对象定义为一个Monitor对象,每一个Monitor对象都会有一个Monitor锁,只要拿到该Monitor锁的线程才可以进入同步代码块或方法执行。此外同步方法或者代码块可以依据Monitor对象相关的Monitor Conditions来决定线程是否阻塞和执行。
Monitor Object模式的主要角色:
Java中的Monitor的实现如下,该实现位于JDK源码hotspot/src/share/vm/runtime/objectMonitor.hpp,如下:
ObjectMonitor() {
_header = NULL;
_count = 0; // 计数器
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL; // 持有锁的线程
_WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
4. synchronized的实现原理
private final Object lock = new Object();
public int incrementAndGet(int a) {
synchronized (lock) {
return a + 1;
}
}
熟悉Java的读者对上述代码必定不会陌生,当大家敲下这段代码的时候有思考过以下问题么?
synchronized的修饰方法和同步代码块本质上是没有什么区别的,都是对指定对象的Monitor锁的获取,只有获得对象的Monitor锁的线程才可以进入同步方法或者同步代码块执行,其他获取失败的线程会被阻塞放入Blocked队列。
4.1 同步代码块和同步方法区别
synchronized的修饰方法在经过编译后,在方法的标志位上会有一个ACC_SYNCHRONIZED来标明该方法是一个同步方法。
synchronized修饰的方法调用步骤:
synchronized同步代码块时在字节码层面是通过monitorenter和monitorexit指令来实现的,synchronized修饰方法时并不需要通过字节码指令。
synchronized同步代码块的调用步骤:
5. 锁优化
synchronized在JDK6以前是一个重量级锁,效率低下,因为Monitor锁是依赖于操作系统的Mutex Lock来实现的,操作系统的线程间的切换需要在用户态和内核态间互相转换,这种转换是比较耗时的。
在JDK6以后,当我们使用synchronized时,JVM会对锁进行优化,优化需要依赖Java对象头的结构,不了解Java对象的戳这里,下一篇我们会讲述JVM是如何进行锁优化的。
本期的Java悲观锁介绍到这,我是shysh95,我们下期再见!!!