Java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor〉是依赖于底层的操作系统的MutexLock(系统互斥量)来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因。在Java6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁。

Monitor可以理解为一种同步工具,也可以理解为一种同步机制。每个Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中,每一个Java对象在对象头中的对象标记中都带了一把看不见的锁,它叫做内部锁或者Monitor锁。

Monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换,成本很高。

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;
}

如果一个java对象被某个线程锁住,则该对象的Mark Work字段中LockWord指向monitor的起始地址

锁的升级

Untitled

1. 无锁态

无锁态的内存布局如下:

# 下面是对象的哈希码
10进制: 1485955886
16进制: 5891e32e
2进制: 1011000100100011110001100101110
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 2e e3 91 (00000001 00101110 11100011 10010001) (-1847382527)
      4     4        (object header)                           58 00 00 00 (01011000 00000000 00000000 00000000) (88)
      8     4        (object header)                           00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Process finished with exit code 0

注:对象的hashCode需要调用hashCode()一次后才会生成,否则则是0

在阅读是需要从高位开始读,对于每个字节从低位开始读

例如该对象的hashCode为:
	 1011000 10010001 11100011 00101110
从右下角开始读,去掉前面24位:
	01011000 10010001 11100011 00101110
可以发现,去掉最前面的0之后,hashCode就相同了

2. 偏向锁

当线程A第一次竞争到锁是,通过修改Mark Word中的偏向线程ID、偏向模式,如果不存在其它线程竞争,那么持有偏向锁的线程将永远不需要进行同步。

当一段同步代码一直被同一个线程多次访问,由于只有一个线程,那么该线程在后续访问时便会自动获得锁。

在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程。

那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接会去检查锁的MarkWord里面是不是放的自己的线程ID)。