关于java的Synchronized,你可能需要知道这些(下)

  上一篇文章介绍了synchronized的基本使用方法和实现,在实现部分说明了synchronized的底层实现依赖系统互斥锁mutex,但是这个一个重型锁,竞争导致线程阻塞挂起,后续拿到锁后再恢复线程,因为java使用的是1对1的线程模型,这个过程页涉及到了用户态和内核态的转换,比较消耗性能。为了解决这个问题,java1.6引入了“轻量级锁”、“偏向锁”。

1.锁优化

  锁的状态分为四种:无锁、偏向锁、轻量级锁,重量级锁,四种锁状态对性能的消耗依次增高,状态的转化也是由无锁向重量级锁转化,这个过程称之为锁的升级,不会出现锁的降级,除非解锁了向无锁状态转换,四种锁状态下对象头前32bit如下(以32bit为例)

锁状态

25 bit

4bit

1bit

2bit

23bit

2bit

是否是偏向锁

锁标志位

轻量级锁

指向栈中锁记录的指针

00

重量级锁

指向互斥量(重量级锁)的指针

10

GC标记

11

偏向锁

线程ID

Epoch

对象分代年龄

1

01

无锁

对象的hashCode

对象分代年龄

0

01

  锁状态转化如下图所示:关于java的Synchronized,你可能需要知道这些(下)

  基本的转化流程如下(详细可以参考:http://www.cnblogs.com/paddix/p/5405678.html):

  偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。
  一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个
线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将
对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。
  一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
  轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。
2.自旋锁、锁粗化和锁消除
  自旋锁和自适应自旋:从上图可以看到,轻量级锁就是一个自旋锁,当加锁的对象已经被加锁(轻量级锁加锁)的时候,当前线程自旋等待,直到获得锁或者锁升级。虚拟机会根据以往的自旋锁获得的情况,设置下次获取锁时自旋等待时间,如果上一次自旋锁加锁成功,会增加下一次自旋时间,如果上一次自旋锁加锁失败,会减少下一次自旋锁自旋时间。
  锁粗化:连续多个加锁、解锁操作导致性能下降,这是虚拟机会根据情况,将整个代码块一起同步,一次加锁解锁,降低性能消耗。
  锁消除:对于不需要同步的代码块,消除加锁解锁。主要基于逃逸技术分析,认为该代码块只会在当前线程访问,无需与其他线程同步,无需保证线程安全。
 
参考:
http://www.cnblogs.com/paddix/p/5405678.html
https://www.zhihu.com/question/53826114
《深入理解java虚拟机》
上一篇:c# winform 根据窗体自动调整控件


下一篇:C# WPF MVVM 实战 – 5- 用绑定,通过 VM 设置 View 的控件焦点