如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 那么synchronized是消耗在哪里呢?
1.线程切换的花费。用户态切换到内核态。
jdk1.6,为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量锁”,在1.6中,锁一共有四中状态,级别从低到高依次是:无锁状态,偏向锁状态,轻量级锁,重量级锁。这几个状态会随着竞争情况逐渐升级,锁可以升级,但是不可以降级,这种不能降级的策略是为了提高获得锁,释放锁的效率。
一,自旋锁
阻塞和唤醒,需要cpu从用户态转换为核心态,频繁的切换,很消耗时间,说不定一个程序占用锁的时间没有那么多,还不如间歇访问(while(1)),自旋锁就是这个概念,它遇到锁不可得的状态时,不会马上就阻塞,而是进行自旋,执行一段无意义的循环,
自旋不可以代替阻塞,因为线程全部都自旋,那么cpu处理的线程会暴增,最后卡死。所以还是需要放到阻塞队列里面等待。
二,适应自旋锁
自旋锁是升级版,自旋的次数不固定,这是由同一个锁上的自旋时间及锁的拥有者的状态来决定。
三,锁消除
jvm检测到不可能存在共享数据竞争,这个时候jvm就会对这些同步锁进行锁消除,锁消除的依据是逃逸分析的数据支持,
1.如果不存在竞争,为什么还需要加锁呢?
:我们虽然没有显示使用锁,但是我们在调用一些jdk的内置api的时候,如StringBuffer,Vector,HashTable之类的,存在隐形加索操作。
四,锁粗化
将多个连续的加索,解锁操作,连接在一起,扩展成一个范围更大的锁。
五,偏向锁
HosSpot作者发现,大多数情况下,锁不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低,引入了偏向锁。我们最开始学习synchronized的时候,介绍过mark word,在这个里面,存在这四种锁的标识,偏向锁内容块存放的是:拥有偏向锁的线程ID,以后该线程进入和退出同步代码块不需要进行cas操作来加锁和解锁,只需要简单地测试一下对象头的mark word存放的标志,(还有一个标志是存放,使用的哪一种锁的),如果没有设置为偏向锁,则需要使用cas竞争锁。
偏向锁的撤销:等到竞争出现才释放锁,偏向锁的撤销,需要等待全局安全点(没有正在执行的字节码),首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程没有活动,则将对象头设置成为无锁状态,如果线程活着,拥有锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的mark word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁(锁升级),最后唤醒暂停的线程。
偏向锁的关闭:在jdk5之中默认是关闭的,在jdk6是默认开启的,如果你的项目所有锁都处于竞争状态,则可以关闭偏向锁,直接进入轻量级锁。
六,轻量级锁
1.加锁: 线程在执行同步代码块之前,jvm会先在当前线程中的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark word复制到锁记录中,官方称为:displaced mark word。然后线程尝试使用cas将对象头中的Mark word替换为指向锁记录的指针。
如果成功,则获得锁,如果失败,表示其他线程在竞争锁,当前线程便尝试使用自旋来获取锁。
2.解锁:解锁时,会使用cas操作将displaced mark word替换回到对象头中,如果成功,则没有竞争。如果失败,表示存在竞争,锁就会升级称为重量级锁。
七,重量级锁
synchronized通过对象内部的monitor实现的,也就是最先版本的synchronized,