踩坑误区
不管是synchronize用在对象或者类对象中,最后是在方法中。都是对对象加锁。获取他的实例方法。
1.6之前
之前的synchronized都是重量级锁,1.6改良了 ,先是偏向锁到轻量级锁,到最后的重量级锁。
偏向锁
不过,当线程执行到临界区(critical section)时,此时会利用**CAS(Compare and Swap)**操作,将线程ID插入到Markword中,同时修改偏向锁的标志位。
如果有线程获取到锁了。
那么,什么是偏向锁
偏向锁是jdk1.6引入的一项锁优化,其中的“偏”是偏心的偏。它的意思就是说,这个锁会偏向于第一个获得它的线程,在接下来的执行过程中,假如该锁没有被其他线程所获取,没有其他线程来竞争该锁,那么持有偏向锁的线程将永远不需要进行同步操作。
在此线程之后的执行过程中,如果再次进入或者退出同一段同步块代码,并不再需要去进行加锁或者解锁操作,而是会做以下的步骤:
Load-and-test,也就是简单判断一下当前线程id是否与Markword当中的线程id是否一致.
如果一致,则说明此线程已经成功获得了锁,继续执行下面的代码.
如果不一致,则要检查一下对象是否还是可偏向,即“是否偏向锁”标志位的值。
如果还未偏向,则利用CAS操作来竞争锁,也即是第一次获取锁时的操作。
锁膨胀
刚才说了,当出现有两个线程来竞争锁的话,那么偏向锁就失效了,此时锁就会膨胀,升级为轻量级锁。这也是我们经常所说的锁膨胀
锁撤销
由于偏向锁失效了,那么接下来就得把该锁撤销,锁撤销的开销花费还是挺大的,其大概的过程如下:
在一个安全点停止拥有锁的线程。
遍历线程栈,如果存在锁记录的话,需要修复锁记录和Markword,使其变成无锁状态。
唤醒当前线程,将当前锁升级成轻量级锁。
轻量级锁
锁撤销升级为轻量级锁之后,那么对象的Markword也会进行相应的的变化。下面先简单描述下锁撤销之后,升级为轻量级锁的过程:
线程在自己的栈桢中创建锁记录 LockRecord。
将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中。
将锁记录中的Owner指针指向锁对象。
将锁对象的对象头的MarkWord替换为指向锁记录的指针。
轻量级锁分两种锁
自旋锁与自适应自选锁
自适应自旋锁
假如一个线程1刚刚成功获得一个锁,当它把锁释放了之后,线程2获得该锁,并且线程2在运行的过程中,此时线程1又想来获得该锁了,但线程2还没有释放该锁,所以线程1只能自旋等待,但是虚拟机认为,由于线程1刚刚获得过该锁,那么虚拟机觉得线程1这次自旋也是很有可能能够再次成功获得该锁的,所以会延长线程1自旋的次数。
轻量级锁也被称为非阻塞同步、乐观锁,因为这个过程并没有把线程阻塞挂起,而是让线程空循环等待,串行执行。
为什么说重量级锁开销大呢
主要是,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cup。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。