synchronized是JVM内置锁,基于monitor机制实现,依赖底层操作系统的互斥源语Mutex(互斥量),它是一个重量级锁,性能较低。
当然,JVM内置锁在1.5之后版本做了重大优化,如锁粗化,锁消除、轻量级锁、偏向锁、自适应锁、等技术来减少操作的开销,内置锁的并发性能已经基本与lock持平。
java虚拟机通过一个同步结构支持方法和方法中的指令序列的同步:monitor。
同步方法是通过方法access_flags中设置ACC_SYNCHRONIZED标志来实现;
同步代码块是通过 ** monitorenter ** 和 ** montorexit ** 来实现。
两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现的,被阻塞的线程会被挂起、等待重新调度,
会导致 **用户态和内核态** 两个态之间来回切换,对性能有较大影响。
Monitor (管程/监视器):
Monitor, 直译为监视器,而操作系统领域一般翻译为 管程。 管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。
在java1.5之前,java语言提供的唯一并发语言就是管程,java1.5之后提供的SDK并发包也是以管程为基础的。除了java之外,C/C++
C#等高级语言也都是支持管程的。synchronized关键字和wait() notify() notifyAll() 这三个方法是java中实现管程技术的组成部分。
MESA模型:
在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen管程,Hoare模型和MESA模型,现在正在广泛使用的是MESA模型。
管程中引入了条件变量的概念,而且每个条件变量都对应有一个等待队列。条件变量和等待队列的作用是解决线程之间的同步问题
wait()的正确使用姿势:
对于MESA管程来说,有一个编程范式:
while(条件不满足){
wait();
}
唤醒的时间和获取到锁继续执行的时间是不一致的,被唤醒的线程再次执行时可能条件又不满足了,所以循环检验条件。
MESA模型的wait() 方法还有一个超时参数,为了避免线程进入等待队列和永久阻塞。
notify()和notifyAll()分别何时使用:
满足一下三个条件,可以使用notify(), 其余情况尽可能使用notifyAll():
1.所有等待线程拥有相同的等待条件
2.所有等待线程被唤醒后,执行相同的操作
3.只需要唤醒一个线程
java语言的内置管程synchronized
Java参考了MESA模型,语言内置的管程(synchronized)对MESA模型进行了精简。MESA模型中,条件变量可以有多个,Java语言内置的管程里面只用一个条件变量。
Monitor机制在Java中的实现:
java.lang.Object 类定义了wait(), notify(), notifyAll() 方法,这些方法的具体实现,依赖于ObjectMonitor实现,这是JVM 内部基于C++实现的一套机制。
ObjectMonitor 其主要数据结构如何:
ObjectMonitor() {
_header = NULL; //对象头 markOop
_count = 0;
_waiters = 0,
_recursions = 0; // 锁的重入次数
_object = NULL; //存储锁对象
_owner = NULL; // 标识拥有该monitor的线程(当前获取锁的线程)
_WaitSet = NULL; // 等待线程(调用wait)组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多线程竞争锁会先存到这个单向链表中 (FILO栈结构)
FreeNext = NULL ;
_EntryList = NULL ; //存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失败的线程)
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
在获取锁的时候,是将当前线程插入到cxq的头部,而释放锁时候,默认策略Qmode=0是: 如果EntryList为空,则将cxq中的元素按原有顺序插入到EtryList,并唤醒第一个线程,也就是当EntryList为空时,是后来的线程先获取锁。EntryList不为空,直接从EntryList中唤醒线程。