synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下三种形式:
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步代码块,锁是Synchronized括号里配置的对象
在JVM中,synchronized方法同步和代码块同步是基于进入和退出Monitor对象来实现的。其中代码块的同步是使用monitorenter和monitorexit指令实现的,而方法同步的实现在JVM规范中没有详细说明。
Java对象头monitorenter指令是编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何一个对象都有一个monitor与之关联,当一个monitor被持有后,它将处于锁定状态。
synchronized用的锁是存在Java对象头里的。对象头中存储Mark Word、Class metadata Address和Array length(如果对象是数组),虚拟机用两个字宽存储对象头(数组类型用三个字宽)。Mark Word存储对象的hashCode、分代年龄和锁标记位;Class metadata Address存储到对象类型数据的指针;Array length是数组的长度。
锁的升级和对比其中Mark Word里存储的内容会随着锁标志位的变化而变化。
Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁,所以在Java SE 1.6中一共有4中状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。
1.偏向锁锁可以升级不可以降级,目的是为了提高获得和释放锁的效率
经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获取锁的代价更低引入了偏向锁。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录中存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS *** 作来加锁和解锁,只需要简单测试一下对象头的Mark Word里是否存在指向该线程的偏向锁。如果测试成功,表示线程已经获得了锁;如果测试失败,则再测试该锁是否为偏向锁(Mark Word中偏向锁的标识是否为1):如果不是,则使用CAS竞争该锁成为指向当前线程的偏向锁;如果是,同样尝试使用CAS将对象头的偏向锁指向当前线程。
(1)偏向锁的撤销
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点没有正在执行的字节码)。
(2)关闭偏向锁
偏向锁在Java 6和7里是默认启用的,但是它在应用程序启动几秒钟后才激活,如果有必要可以使用JVM参数来关闭延迟。
2.轻量级锁如果你确定应用程序里所有的锁通常情况下处于竞争状态,也可以通过JVM参数关闭偏向锁,那么锁会进入轻量级锁状态。
(1)轻量级加锁
线程在执行代码块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中(官方称为Displaced Mark Word)。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,则获得锁;如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
(2)轻量级解锁
解锁时,会使用原子的CAS *** 作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,则表示当前锁存在竞争,锁就会膨胀为重量级锁。
3.锁的优缺点对比追求响应时间
同步块执行速度非常快
追求吞吐量
同步块执行速度较长
《Java并发编程的艺术》
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)