自旋在synchronized底层实现性能提升

JVM的同步是基于进入与退出监视器对象(管程对象)(Monitor)来实现的,每个对象实例都会有一个Monitor对象,Monitor对象会和Java对象一同创建和销毁。Monitor对象是由C++来实现的。

当多个线程同时访问一段同步代码时,这些线程会被放到一个EntryList集合中,处于阻塞状态的线程都会被放到该列表中。接下来当线程获取到对象的Monitor时,Monitor依赖于底层操作系统的mutex lock来实现互斥的,线程获取mutex成功,则会持有该mutex,这时其他线程就无法再获取到该mutex。

如果线程调用了wait方法,那么该线程就会释放掉所持有的mutex,并且该线程会进入到WaitSet集合(等待集合)中,等待下一次被其他线程调用notify/notifyAll方法来唤醒。如果当前线程顺利执行完方法,那么它也会释放掉所持有的锁。

总结一下:同步锁在这种实现方式当中,由于Monitor是依赖于底层操作系统实现的,这样就会存在用户态和内核态之间的切换,所以会增加性能开销。

通过对象互斥锁的概念来保证共享数据操作的完整性。每个对象都对应于一个可称为互斥锁的标记,这个标记用于保证在任何时刻,只能有一个线程访问对象。

那些处于EntryList与WaitSet中的线程均处于阻塞状态,阻塞操作是由操作系统来完成的。在Linux下是通过pthread_mutex_lock函数实现的。线程被阻塞后便会进入到内核调度状态,这会导致系统在用户态和内核态之间来回切换,严重影响锁的性能。

解决上述问题的方式便是自旋,其原理就是当发生对Monitor的争用时,若Owner(获得锁的线程)能够在很短的时间内释放掉锁,则那些正在竞争的线程就可以稍微等待一下(自旋),在Owner线程释放锁之后,争用线程可能会立刻获取到锁,从而避免了系统阻塞。不过,当Owner运行的时间超过了临界值后,争用线程自旋一段时间后依然无法获取到锁,这时争用线程则会停止自旋而进入到阻塞状态。

所以,总体思路就是先自旋,不成功再进行阻塞,尽量降低阻塞的可能性,这对那些执行时间很短的代码块来说有极大的性能提升。显然,自旋在多处理器(多核心)上才有意义。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章