likes
comments
collection
share

12 深入了解Synchronized同步锁的优化方法

作者站长头像
站长
· 阅读数 40

大家好,我是小水珠。

在并发编程中,多个线程访问同一个共享资源时,我们必须考虑如何维护数据的原子性。在JDK1.5之前,Java是依靠Synchronized关键字实现锁功能来做到这点的。Synchronized是JVM实现的一种内置锁,所得获取和释放是由JVM隐式实现。

到了JDK1.5版本,并发包中新增了Lock接口来实现锁功能,它提供了与Synchronized关键字类似的同步功能,只是在使用时需要显示获取和释放锁。

到了JDK1.6版本后,Java对Synchronized同步锁做了充分的优化,甚至在某些场景下,它的性能已经超越了Lock同步锁。这一讲我们就来看看Synchronized同步锁究竟是通过了哪些优化,实现了性能地提升。

一 Synchronized同步锁实现原理

通常Synchronized实现同步锁的方式有两种,一种是修饰方法,一种是修饰方法块。以下就是通过Synchronized实现的两种同步方法加锁的方式:

12 深入了解Synchronized同步锁的优化方法

下面我们可以通过反编译看下具体的字节码的实现,运行以下反编译命令,就可以输出我们想要的字节码:

12 深入了解Synchronized同步锁的优化方法

通过输出的字节码,你会发现:Synchronized在修饰同步代码块时,是由monitorenter和monitorexit指令来实现同步的。进入monitorenter指令后,线程将持有Monitor对象,退出monitorexit指令后,线程将释放该Monitor对象。

12 深入了解Synchronized同步锁的优化方法

再来看以下同步方法的字节码,你会发现:当Synchronized修饰同步方法时,并没有发现monitorenter和monitorexit指令,而是出现了一个ACC_SYNCHRONIZED标识。 12 深入了解Synchronized同步锁的优化方法

JVM中的同步是基于进入和退出管程对象实现的。每个对象实例都会有一个Monitor,Monitor可以和对象一起创建和销毁。Monitor是由ObjectMonitor实现,而ObjectMonitor是由C++的ObjectMonitor.hpp文件实现,如下所示:

12 深入了解Synchronized同步锁的优化方法

当多个线程同时访问一段同步代码时,多个线程会先被存放在CollectionList集合中,处于block状态的线程,都会被加入到该列表。接下来当线程获取到对象的Monitor时,Monitor是依靠底层操作系统的Mutex Lock来实现互斥的,线程申请Mutex成功,则持有该Mutex,其他线程将无法获取到该Mutex,竞争失败的线程会再次进入ContentionList被挂起。

如果线程调用了wailt()方法,就会释放当前持有的Mutex,并且该线程会进入WaitSet集合中,等待下一次被唤醒。如果当前线程顺利执行完方法,也将释放Mutex。

12 深入了解Synchronized同步锁的优化方法

二 锁升级优化

java对象头

在JDK1.6 JVM中,对象实例在堆内存中被分为了三部分:对象头,实例数据和对齐填充。其中Java对象头由Mark Word,指向类的指针以及数组长度三部分组成。

Mark Word记录了对象和锁有关的信息。Mark Word在64位JVM中的长度是64bit,我们可以一起看下64位JVM的存储结构是怎么样的,如下图所示: 12 深入了解Synchronized同步锁的优化方法

1.偏向锁

下图中红线流程部分为偏向锁获取和撤销流程: 12 深入了解Synchronized同步锁的优化方法

2.轻量级锁

下图中红线流程部分为轻量级锁获取和撤销流程: 12 深入了解Synchronized同步锁的优化方法

3.自旋锁与重量级锁

下图中红线流程部分为自旋后升级为重量级锁的流程: 12 深入了解Synchronized同步锁的优化方法

三 动态编译实现锁消除/锁粗化

除了锁升级优化,Java还使用了编译器对锁进行优化。JIT编译器在动态编译同步代码块的时候,借助了一种被称为逃逸分析的技术,来判断同步块使用的锁对象是否只能被一个线程访问,而没有被发布到其它线程。

确认是的话,那么JIT编译器在编译这个同步块的时候,不会生成Sychronized所表示的锁的申请与释放的机器码。即消除锁的使用。

锁粗化同理。

四 减小锁粒度

最经典的减小锁粒度的案例就是JDK1.8之前实现的ConcurrentHashMap版本。我们知道,HashTable是基于一个数组+链表实现的,所以在高并发读写操作集合时,存在激烈的所资源竞争,也因此性能会存在瓶颈。而ConcurrentHashMap就很巧妙的使用了分段所Segment来降低锁资源竞争,如下图所示: 12 深入了解Synchronized同步锁的优化方法

五 总结

JVM在JDK1.6中引入了分级锁机制来优化Synchronized,当一个线程获取锁时,首先对象锁将成为一个偏向锁,这样做是为了优化同一线程重复获取导致的用户态与内核态的切换问题;其次如果有多个线程竞争锁资源,锁将会升级为轻量级锁,它适用于在短时间内持有锁,且锁有交替切换的场景;轻量级锁还使用了自旋锁来避免线程用户态与内核态的频繁切换,大大的提高了系统性能;但如果锁竞争太激烈了,那么同步锁将会升级为重量级锁。

减少锁竞争,是优化Sychronized同步锁的关键。我们应该尽量使Sychronized同步锁处于轻量级锁或偏向锁,这样才能提高Sychronized同步锁的性能;通过减小锁粒度来降低锁竞争也是一种常用的优化方法;另外我们还可以通过减少锁的持有时间来提高Sychronized同步锁在自旋时获取锁资源的成功率,避免Sychronized同步锁升级为重量级锁。