2. 锁
2.1 无锁
Java对象刚创建时还没有任何线程来竞争,说明该对象处于无锁状态(无线程竞争它),这时偏向锁标识位是0,锁状态是01 。
2.2 偏向锁
偏向锁是指一段同步代码一直被同一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。如果内置锁处于偏向状态,当有一个线程来竞争锁时,先用偏向锁,表示内置锁偏爱这个线程,这个线程要执行该锁关联的同步代码时,不需要再做任何检查和切换。偏向锁在竞争不激烈的情况下效率非常高
当偏向锁没未指定线程的时候,称为匿名偏向。
2.3 轻量级锁
线程轻度竞争,偏向锁升级为轻量级锁。
引入轻量级锁是因为在竞争不激烈的情况下,通过CAS机制竞争锁减少重量级锁的性能损耗。重量级锁使用了操作系统底层的互斥锁(Mutex Lock),会导致线程在用户态和核心态之间频繁切换,从而带来较大的性能损耗。
自旋原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要进行内核态和用户态之间的切换来进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免了用户线程和内核切换的消耗
2.3.1 轻量级锁执行过程
在抢锁线程进入临界区之前,如果内置锁(临界区的同步对象)没有被锁定,JVM首先将在抢锁线程的栈帧中建立一个锁记录(Lock Record
),用于存储对象目前Mark Word的拷贝。
然后抢锁线程将使用CAS
自旋操作,尝试将内置锁对象头的Mark Word
的ptr_to_lock_record
(锁记录指针)更新为抢锁线程栈帧中锁记录的地址,如果这个更新执行成功了,这个线程就拥有了这个对象锁。然后JVM将Mark Word
中的lock标记位改为00(轻量级锁标志),即表示该对象处于轻量级锁状态。抢锁成功之后,JVM会将Mark Word中原来的锁对象信息(如哈希码等)保存在抢锁线程锁记录的Displaced Mark Word
(可以理解为放错地方的Mark Word)字段中,再将抢锁线程中锁记录的owner指针指向锁对象。
2.4. 重量级锁
由于自旋是消耗CPU资源的。如果线程竞争激烈,空转带来的CPU
消耗大大增加,那么CAS
机制的优势将变成劣势,不如将线程放入等待队列,等待系统调度。
JVM
中每个对象都会有一个监视器,监视器和对象一起创建、销毁;在Hotspot
虚拟机中,监视器是由C++
类ObjectMonitor
实现的。
Cxq
:竞争队列(Contention Queue
),所有请求锁的线程首先被放在这个竞争队列中。EntryList:Cxq
中那些有资格成为候选资源的线程被移动到EntryList
中。在Owner线程释放锁时,JVM
会从Cxq
中迁移线程到EntryList,并会指定EntryList中的某个线程(一般为Head)为OnDeck Thread(Ready Thread)
。WaitSet
:某个拥有ObjectMonitor
的线程在调用Object.wait()
方法之后将被阻塞,然后该线程将被放置在WaitSet
链表中。在某个时刻通过Object.notify()
或者Object.notifyAll()
唤醒,该线程就会重新进入EntryList
中。
3. 锁重入
sychronized是可重入锁。上锁的方法中调用了其它方法,也给其它方法上了同样的锁。重入的次数必须记住,因为要解锁几次要对应。记录在:
偏向锁-》线程栈-> LR+1
参考:《Java高并发核心编程(卷2)》