synchronized
在印象中是一个解决线程安全问题对神器,但它的效率却不怎么高。在jdk1.6以后,synchronized
已经不再是一个简单粗暴的线程锁了,通过引入偏向锁/轻量级锁等机制对其进行了一系列优化。
synchronized的用法
synchronized
的用法主要有三种
- 静态同步方法
- 普通同步方法
- 同步代码块
这三种应用形式的区别在于
- 静态同步方法用的锁对象是当前类的class对象
- 普通同步方法用的锁对象是当前对象
- 同步代码块用的锁对象是指定的一个已存在的对象
代码实例如下:
public static synchronized void method1(){
....
}
public synchronized void method2() {
...
}
private Object lockObj = new Object();
public void method3(){
synchronized(lockObj) {
...
}
}
synchronized锁原理
��️上面可知,synchronized
的使用主要分两种,一种是方法级别的,一种是同步代码块,对于这两种方式,底层的实现原理有所不同。
synchronized同步代码块
同步代码块是通过java编译器在临界区的开始和结束位置插入monitorenter
和monitorexit
指令来实现同步的。每个monitorenter
都必须对应一个monitorexit
,又一个每个java对象都有一个monitor与之关联,当执行monitorenter
时,当前线程会尝试持有锁对应对应当monitor,当执行monitorexit
时,会释放已经获取当monitor
synchronized同步方法
同步方法并没有在指令层面假如任何的同步指令,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。
对象头
synchronized用的锁是存储在Java对象头里面的,Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。其中Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键。
对象头信息是与对象自身定义的数据无关的额外存储成本,但是考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间,也就是说,Mark Word会随着程序的运行发生变化,变化状态如下(32位虚拟机):
参考:ImportNew
synchronized锁的种类
在jdk1.6中引入了偏向锁/轻量级锁来优化synchronized原有的单一重量级锁,这几种锁的级别从底到高分别为 偏向锁->轻量级锁->重量级锁,随着对共享资源竞争激烈程度的怎加锁的级别逐渐增加,直到重量级锁。
锁可以升级,但不能降级,也就是不能从重量级锁降级为偏向锁。
偏向锁
引入偏向锁的目的是减少在无竞争状态下获取锁和释放锁的开销。
获取偏向锁的流程:
- 检查锁对象的MarkWorld是否为偏向锁
- 如果是偏向锁,则检查偏向的线程id是否为当前线程id
- 如果偏向锁指向当前线程,则直接进入临界区
- 如果偏向锁指向其他线程,则当前线程通过cas竞争偏向锁,也就是将锁偏向的线程id指向当前线程id
- 如果设置成功,则竞争锁成功,进入临界区
- 如果设置失败,则说明共享资源出现竞态,将锁升级为轻量级锁
偏向锁的获取流程:
偏向锁的释放采用来一种只有出现竞争才释放锁的策略,偏向锁不会被主动释放,偏向锁的释放需要等待一个全局安全点,也就是一个稳定的状态
- 挂起持有偏向锁的线程,判断锁对象释放处于锁定状态
- 撤销偏向锁,恢复到无锁状态或升级为轻量级锁
轻量级锁
轻量级锁也是为了解决重量级锁的性能问题, 针对共享资源的竞争但线程的任务很轻,很快就会执行完情况。
轻量级锁的获取流程:
- 判断当前锁是否处于锁定状态,如果是,则在当前线程的栈桢上创建一个名为锁记录的空间,用来存储锁对象的mark word的拷贝
- 利用cas将锁对象中的mark workd指向当前线程的锁记录(mark word的拷贝),如果设置整个则获取锁成功
- 如果设置失败,则判断当前锁对象的mark word是否指向当前线程的锁记录,如果是,则说明当前线程之前已经获取过锁,直接进入临界区,如果不是,则说明存在竞争条件,将锁升级为重量级锁
轻量级锁的是否过程
- 取出在获取轻量级锁保存在Displaced Mark Word中的数据;
- 用CAS操作将取出的数据替换当前对象的Mark Word中,如果成功,则说明释放锁成功,
- 如果CAS操作替换失败,说明有其他线程尝试获取该锁,则需要在释放锁的同时需要唤醒被挂起的线程
锁总结
- 偏向锁适用于单线程访问临界区情况
- 轻量级锁适用于存在并发,但由于临界区执行速度很快不会出现竞争但情况
- 重量级锁就是传统的锁,适用于任何场景,只是由于线程会被挂起,带来额外的开销以及影响系统的吞吐率