Java multithreading lock optimization strategy and lock expansion

Please indicate the original address for reprint: http://www.cnblogs.com/ygj0930/p/6561264.html 

    Lock optimization strategy

    The lock optimization ideas that can be taken in the coding process are as follows:

    1: Reduce lock holding time 

         For example, locking a method is not as good as locking a few lines of code in the method that need to be synchronized;

    2: Reduce lock granularity

        For example: ConcurrentHashMap adopts locking on segments instead of the entire map to improve concurrency;

    3: lock separation 

        According to the nature of synchronization operations, locks are divided into read locks and write locks. Read locks are not mutually exclusive, which improves concurrency.

    4: Chain coarsening 

        This seems to conflict with idea 1, but it is not. Idea 1 is for only a few places in a thread that need to be synchronized, so the lock is added to the synchronized statement instead of a larger scope, reducing the time that the thread holds the lock;

        Lock coarsening refers to: in a thread that needs to execute synchronization statements at intervals, it is very performance-intensive to frequently lock and unlock between discontinuous synchronization blocks. Synchronized statements perform one-time lock and unlock. Although the time that the thread holds the lock is increased, it is generally optimized.

    5: Lock Elimination

        Lock elimination is what the compiler does: according to the code escape technique, if it is judged that in a piece of code, the data on the heap will not escape the current thread (that is, it will not affect the data outside the thread space), then this code can be considered as Thread-safe, no need to lock.

 

    The lock optimization strategy adopted in the Java virtual machine :

       1: Biased lock: The lock object is biased towards the thread that currently obtains it. If it is not requested by other threads in the next, the thread holding the lock will no longer need to perform synchronization operations (ie: the thread holding the lock is in the When the synchronized block is encountered in the next execution, lock and unlock are no longer needed, and it can be executed directly). When another thread applies for the lock, the bias mode of the current thread will end and the lock will be released.

       2: Lightweight lock: The underlying implementation of syncrhoized is controlled by the monitor monitor, and the two primitives monitorenter and monitorexit are implemented by relying on the mutual exclusion (mutex) of the operating system.

Mutual exclusion will cause the thread to hang and need to be rescheduled back to the original thread in a short period of time, which consumes more resources. Lightweight Locking uses the CPU primitive Compare-And-Swap (CAS, assembly instruction CMPXCHG) to try to remedy before entering the mutex and reduce the probability of multi-thread entering the mutex.

        If the biased lock fails, the system will perform a lightweight lock operation and use the CAS operation to try to lock. If the lightweight lock fails, the system-level heavyweight lock (syncrhoized) is called to lock.     

       3: Spin lock: When a thread applies for a lock and the lock is occupied, let the current thread execute a busy loop (spin) to see if the thread holding the lock will release the lock soon. If the lock has not been acquired after the spin, it will enter the synchronous blocking state;

           3.1: Adaptive spin: The time for a spinning thread to spin is the time it takes for a thread to spin on the same lock and acquire the lock. If the spin is rarely successful for this lock, it will not spin to avoid wasting CPU resources.

         In order to try to avoid using heavyweight locks (mutual exclusion at the operating system level), the JVM will first try a lightweight lock, and the lightweight lock will try to use the CAS operation to acquire the lock. If the lightweight lock fails to be acquired, it means that there is a competition. But maybe soon to get the lock, try a spinlock, do a few empty loops with the thread, and keep trying to get the lock each time you loop. If the spin lock also fails, it can only be upgraded to a heavyweight lock.


First, let's talk about the optimization strategy of the lock.

1. Spin lock

The self-selected lock actually means that when you take the lock, you find that a thread has already taken the lock. If you go to take it, you will block yourself. At this time, you will choose to perform a busy loop attempt. That is, it keeps looping to see if it can wait until the previous thread releases the lock by itself. This problem is based on a practical consideration: many threads that take locks will release locks very quickly. Because there are not many sensitive operations in general. Of course, this is a situation that cannot be completely determined, and it can only be said that it is an optimization in general.

举个例子就好比一个人要上厕所发现厕所里面有人,他可以:1,等一小会。2,跑去另外的地方上厕所。等一小会不一定能等到前一个人出来,不过如果跑去别的厕所的花费的时间肯定比等一小会结果前一个人出来了长。当然等完了结果那个人没出来还是要跑去别的地方上厕所这是最慢的。

然后是基于这种做法的一个优化:自适应自旋锁。也就是说,第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。道理是:一个锁如果能够在自旋的过程中被释放说明很有可能下一次也会发生这种事。那么就更要给这个锁某种“便利”方便其不阻塞得锁(毕竟快了很多)。同样如果多次尝试的结果是完全不能自旋等到其释放锁,那么就说明很有可能这个临界区里面的操作比较耗时间。就减小自旋的次数,因为其可能性太小了。

2,锁粗化

试想有一个循环,循环里面是一些敏感操作,有的人就在循环里面写上了synchronized关键字。这样确实没错不过效率也许会很低,因为其频繁地拿锁释放锁。要知道锁的取得(假如只考虑重量级MutexLock)是需要操作系统调用的,从用户态进入内核态,开销很大。于是针对这种情况也许虚拟机发现了之后会适当扩大加锁的范围(所以叫锁粗化)以避免频繁的拿锁释放锁的过程。

3,锁消除

通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。

4,偏向锁和轻量级锁

这两个锁既是一种优化策略,也是一种膨胀过程所以一起说。首先它们的关系是:最高效的是偏向锁,尽量使用偏向锁,如果不能(发生了竞争)就膨胀为轻量级锁,这样优化的效率不如原来高不过还是一种优化(对比重量级锁而言)。所以整个过程是尽可能地优化。

首先说说偏向锁。

HotSpot的研究人员发现大多数情况下虽然加了锁,但是没有竞争的发生,甚至是同一个线程反复获得这个锁。那么偏向锁就为了针对这种情况。

举个例子,一个仓库管理员管着钥匙,然而每一次都是老王去借,仓库管理员于是就认识了老王,直接和他说,“行,你直接拿就是不用填表格了我记得你”。

讲一下偏向锁的具体过程。首先JVM要设置为可用偏向锁。然后当一个进程访问同步块并且获得锁的时候,会在对象头和栈帧的锁记录里面储存取得偏向锁的线程ID。

下一次有线程尝试获取锁的时候,首先检查这个对象头的MarkWord是不是储存着这个线程的ID。如果是,那么直接进去而不需要任何别的操作。如果不是,那么分为两种情况。1,对象的偏向锁标志位为0(当前不是偏向锁),说明发生了竞争,已经膨胀为轻量级锁,这时使用CAS操作尝试获得锁(这个操作具体是轻量级锁的获得锁的过程下面讲)。2,偏向锁标志位为1,说明还是偏向锁不过请求的线程不是原来那个了。这时只需要使用CAS尝试把对象头偏向锁从原来那个线程指向目前求锁的线程。这种情况举个例子就是老王准备退休了,他儿子接替他来拿钥匙,于是仓库管理员认识了他儿子,他儿子每次来也不用登记注册了。

这个CAS失败了呢?首先必须明确这个CAS为什么会失败,也就是说发生了竞争,有别的线程和它抢锁并且抢赢了,那么这个情况下,它就会要求撤销偏向锁(因为发生了竞争)。接着它首先暂停拥有偏向锁的线程,检查这个线程是否是个活动线程,如果不是,那么好,你拿了锁但是没在干事,锁还记录着你,那么直接把对象头设置为无锁状态重新来过。如果还是活动线程,先遍历栈帧里面的锁记录,让这个偏向锁变为无锁状态,然后恢复线程。

再说轻量级锁。这是偏向锁膨胀之后的产物。

加锁的过程:JVM在当前线程的栈帧中创建用于储存锁记录的空间(LockRecord),然后把MarkWord放进去,同时生成一个叫Owner的指针指向那个加锁的对象,同时用CAS尝试把对象头的MarkWord成一个指向锁记录的指针。成功了就拿到了锁。那么失败了呢?失败了的说法比较多。主流有《深入理解JVM》的说法和《并发编程的艺术》的说法。

《深入理解JVM》的说法:

Failed, go to see the value of MarkWord. There are 2 possibilities: 1, a pointer to the current thread, 2, something else.

If it is 1, it means that a "re-entrance" situation has occurred, and it is directly treated as a successful lock acquisition.

In fact, there is a question about why the lock was obtained successfully and CAS failed. In fact, the specific process of CAS is involved here: first compare whether a certain value is the predicted value, and if so, use atomic operation exchange (or assignment), otherwise No operation directly returns failure. The expected value when using CAS is its original MarkWord. When "reentrancy" occurs, it will be found that its value is not the expected original MarkWord, but a pointer, so of course it returns failure, but if the pointer points to this thread, it means that the lock has actually been acquired, but it is re-entry . If not this thread, then case 2:

If it is 2, then there is a competition, and the lock will expand to a heavyweight lock (MutexLock)

The Art of Concurrent Programming says:

Failed to spin directly. It is expected that the lock will be acquired within the time of the spin. If it still cannot be acquired, it will start to expand, change the MarkWord of the lock to the pointer of the heavyweight lock, and block itself.

Unlocking process: (the thread that got the lock) uses CAS to change the MarkWord back to the original object header. If it succeeds, then there is no competition and the unlocking is completed. If it fails, it means that there is a competition (there was a thread trying to modify MarkWord through CAS), then the lock is released and the blocked thread is woken up.

 


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325644006&siteId=291194637