1、自旋
在多核处理器系统中,如果线程1、线程2争抢同一资源A,线程1在核core1中获得了A的锁,此时如果线程2在核core2中想要尝试获得A的锁,那么一般情况是会阻塞同步,让出处理器资源。自旋则是如果没有获得锁,则进行补偿,即循环等待而不让出处理器资源。这样可以减少线程间的切换,一定程度上提高性能。但是,如果不能及时获取锁,也会浪费处理器资源,也就有了适应性自旋。
2、适应性自旋
适应性自旋会根据前面线程获取锁的情况来调整循环等待的时间(通过JVM对运行环境的监控来判断吧)。如果前一次获取该锁的线程刚刚得到了锁,并且线程仍然在正常执行。则认为这一次获取锁也可以成功,相应的循环等待的时间就会相对较长。如果想要获取该锁的线程很少成功,循环等待的时间就会比较短。
虽然在源代码中对某些资源进行了同步,但是JVM检测到并不会发生线程间的资源共享,会把同步块去掉。
锁粗化是说 一串代码分了很多次对同一个对象进行同步,或者说循环体内进行同步。则JVM会保证同步正确的情况下把同步的代码范围增大,去掉中间过多的同步块,只保留范围最大的。循环体内的同步放到循环体外。
轻量级锁涉及到对象头信息结构、CAS操作等
1、对象头信息结构
java对象头信息分两部分,一部分是对象运行时数据,如GC次数,该对象的hashCode码等,该部分称为“Mark Word”,与轻量级锁的实现紧密相关。另一部分是指向方法区该类类型的指针,作为访问类信息的接口。
在32位的HotSpot虚拟机中Mark Word为32bits(64虚拟机为64bits),在对象未被锁定的情况下,其中25bits用于存hashCode码,4bits存分代年龄,2bits存锁标志位1bit固定为0;由于Mark Word为非固定数据结构,在其他情况下存储内容如下图所示(截自书籍:深入理解java虚拟机):
Mark Word
2、CAS操作
CAS即Comapare-and-Swap,比较并交换。java封装了硬件指令支持的原子操作CAS。CAS操作的大意是:对于某个变量有地址V,有一个旧的预期值A,一个待更新的值B。当且仅当V处有值A,才用B进行更新,并返回旧值A。如果V处不是A,则不更新,并返回A。
CAS操作的问题:ABA问题,即实际上之前已经对旧值进行了修改,但是按照判定规则却是没有更改的,该问题在java的concurrent包中有解决。另外该操作只能处理一个对象。
3、轻量级锁的实现
如果线程获取锁对象时,该对象没有上锁,即标志位为“01”,此时会在当前线程的栈帧中建立名为“Lock Record”的锁记录区域,用于存储对象中的Mark Word。然后虚拟机使用CAS操作更新栈帧中的锁记录区域。
如果更新成功,则该线程获得该锁,可以进入同步区,并且对象头中的Mark Word修改为指向当前线程栈帧的指针,锁标志位变为“00”,即轻量级锁定。
如果更新失败,虚拟机首先检查对象头的Mark Word是否指向当前线程的栈帧,若是,说明当前线程以持有锁可直接进入同步区,否则说明有其他线程在争抢该锁。如果有其他线程争抢同一把锁,那么轻量级锁将不再起作用,而是转变为重量级锁(锁标志为“10”)。变为重量级锁后,Mark Word变为指向重量级锁(互斥量)的指针,后面未获得锁的线程会被阻塞。
4、解锁操作
解锁操作同样通过CAS来进行。如果对象头的Mark Word仍然指向线程栈帧的Lock Record区域,则使用CAS操作更新对象头中的Mark Word为Lock Record区域的内容。如果更新成功,整个同步过程完成。如果更新失败,说明还有其他线程尝试获取该锁,那么在释放锁的同时还要唤醒阻塞的线程。
5、轻量级锁提高性能的依据
轻量级锁认为大多数锁在对象同步周期内不会出现竞争的情况,即其他线程不会尝试获取已上锁 共享资源。这样同步的过程就只会使用到CAS操作。
6、轻量级锁潜在的问题
存在大量锁竞争的时候,不仅存在CAS操作,还会额外产生使用互斥量来进行同步的性能消耗。
偏向锁为了消除在数据没有竞争的情况下的同步原语。正如其字面意思,偏向锁会偏向于第一个获取到锁的线程。线程获取到锁后(同样涉及线程栈帧的Lock Record区域和CAS操作),把锁标志位置为“01”(偏向锁),如果接下来的执行过程中,该锁没有被其他线程获取,持有偏向锁的线程不再需要对该锁对象进行同步。
当有其他线程尝试获取锁时,偏向锁将不再起作用。根据对象是否处于锁定状态,撤销偏向恢复到未锁定(标志位"01")或者轻量级锁(标志位“00”)。
偏向锁可以提高有同步但无数据竞争的程序性能(有这样的程序?)。