基本介绍
synchronized是Java实现同步的一种机制,它属于Java中关键字,是一种jvm级别的锁。synchronized锁的创建和释放是此关键字控制的代码的开始和结束位置,锁是有jvm控制的创建和释放的,正是因为这一点,synchronized锁不需要手动释放,哪怕是代码出现异常,jvm也能自动释放锁。同时jvm也记录的使用锁的线程,以及哪些线程出现了死锁这非常有利于我们排查问题。
优缺点
优点
1.synchronized所不用手动释放锁,即便抛出异常jvm也是让线程自动释放锁
2.当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源
缺点
1.使用synchronized如果其中一个线程不释放锁,那么其他需要获取锁的线程会一直等待下去,等待的线程不能中途中断,直到使用完释放或者出现异常jvm会让线程自动释放锁
2.也无法通过投票得到锁,如果不想等下去,也就没法得到锁
3.同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况
4.在激烈争用情况下更佳的性能,也即是如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作,性能比较低。因为当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息,这些虽然对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源,但是这势必会增大资源的消耗和耗时增加。
synchronized中锁的级别
偏向锁
HotSpot作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入偏向锁。
线程1检查对象头中的Mark Word中是否存储了线程1,如果没有则CAS操作将Mark Word中的线程ID替换为线程1。此时,锁偏向线程1,后面该线程进入同步块时不需要进行CAS操作,只需要简单的测试一下Mark Word中是否存储指向当前线程的偏向锁,如果成功表明该线程已经获得锁。如果失败,则再需要测试一下Mark Word中偏向锁标识是否设置为1(是否是偏向锁),如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将偏向锁指向当前线程
偏向锁的竞争结果:
根据持有偏向锁的线程是否存活
1.如果不活动,偏向锁撤销到无锁状态,再偏向到其他线程
2.如果线程仍然活着,则升级到轻量级锁
偏向锁在Java6和Java7中默认是开启的,但是在应用程序启动几秒后才激活,如果有必要可以关闭延迟:
-XX:BiasedLockingStartupDelay=0
如果确定应用程序中所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:
-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁。
-XX:BiasedLockingStartupDelay=0 -XX:+TraceBiasedLocking
轻量级锁
1.线程在执行同步块之前,JVM会在当前栈桢中创建用于存储锁记录的空间(Lock record),并将对象头中的Mark Word复制到锁记录中(Displaced Mark Word)。
2.然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针
3.如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程尝试使用自旋来获取锁
重量级锁
偏向锁、轻量级锁、重量级锁的优缺点
1.偏向锁是为了避免某个线程反复获得/释放同一把锁时的性能消耗,如果仍然是同个线程去获得这个锁,尝试偏向锁时会直接进入同步块,不需要再次获得锁。
2.而轻量级锁和自旋锁都是为了避免直接调用操作系统层面的互斥操作,因为挂起线程是一个很耗资源的操作。
为了尽量避免使用重量级锁(操作系统层面的互斥),首先会尝试轻量级锁,轻量级锁会尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。
3.可见偏向锁,轻量级锁,自旋锁都是乐观锁。