Synchronized underlying optimization (lightweight lock, biased lock)

 

A heavyweight lock

  Last article to introduce the principle of Synchronized usage and implementation. Now we should know, Synchronized is called a lock monitor (monitor) by an internal target to achieve. But the essence of the monitor lock is dependent on the underlying operating system Mutex Lock to achieve. The reason for the operating system to switch between threads which need to be converted from user mode to kernel mode, this cost is very high, transitions between states require a relatively long period of time, which is why low Synchronized efficiency. Therefore, this depends on the operating system Mutex Lock Lock realized we call "heavyweight lock." Synchronized to the JDK to do all kinds of optimization, the core is to reduce the use of such a heavyweight lock. After JDK1.6, in order to obtain and release locks reduce performance brought consumption, improve performance, the introduction of "lightweight lock" and "biased locking."

Second, lightweight lock 

  There are four state of the lock: no lock status, tend to lock, lightweight and heavyweight lock lock. With the lock of the competition, you can upgrade from lock to lock biased lightweight lock, then upgrade heavyweight lock (lock but the upgrade is unidirectional, that is to say only upgrade from low to high, the lock will not appear downgrade). JDK 1.6 is enabled by default in biased locking lock and lightweight, we can also -XX: Disable biased locking -UseBiasedLocking. The lock state is stored in the file header object to the JDK Example 32:

Lock status

25 bit

4bit

1bit

2bit

23bit

2bit

Whether it is biased locking

Lock flag

Lightweight lock

Pointing the stack pointer record lock

00

Heavyweight lock

Mutex pointer (heavyweight lock) of

10

GC mark

air

11

Biased locking

Thread ID

Epoch

Object generational Age

1

01

no lock

hashCode object

Object generational Age

0

01

  "Lightweight" is relative to the use of the operating system mutex to achieve the traditional lock. However, you first need to stress that it is not intended to replace the lock lightweight heavyweight lock, it was intended under the premise of no multithreading competition, reduce the use of traditional heavyweight lock on performance of consumption. Prior to the implementation explain lightweight lock, first understand that the lightweight lock the adaptation scenario is the case thread alternately perform synchronization blocks, the situation if there is access to the same lock at the same time, it will lead to the lightweight lock inflation heavyweight lock.

1, lightweight lock locking procedure

  (1) When the code to enter the synchronized block, if the synchronization object lock status is no lock state (lock flag is "01" state, whether it is biased locking "0"), the virtual machine first stack frame in the current thread establishing a record called lock (lock record) space for storing a copy of the current lock object of Mark Word, officially known as Displaced Mark Word. At this time the state of the object header with a thread stack shown in Figure 2.1.

  (2) copy target header Mark Word copied into the lock record.

  (3) the copy is successful, the virtual machine will try to use the CAS operation target Mark Word updated to point to Lock Record pointer, and Lock record in the owner pointer to the object mark word. If the update is successful, proceed to step (3), otherwise step (4).

  (4) If the update action is successful, then the thread owns the lock of the object, and the object of Mark Word lock flag is set to "00", it means that this object is lightweight locked state, this time the thread stack state of an object head shown in Figure 2.2.

  (5) If this update fails, the virtual machine will first check whether the object of Mark Word points to the current thread's stack frame, if it means the current thread already owns the lock of this object, it can directly enter a synchronization block continues . Otherwise, a plurality of threads described lock contention, lightweight expanded heavyweight lock must lock, lock flag state value becomes "10", Mark Word is stored pointing heavyweight lock (mutex) pointer back threads waiting for the lock should enter the blocked state. The current thread will try to use spin to acquire the lock is to prevent the spin thread is blocked, and the use of the process cycle to acquire the lock.

 

                     Figure 2.1 CAS lightweight lock operation before the stack state object

   

                      Stack state of the object after the operation of FIG lightweight lock CAS 2.2

2, lightweight lock unlocking process:

  (1) by the CAS operation attempts to replace the current thread Mark Word Displaced Mark Word copied object.

  (2) if the replacement is successful, the entire synchronization process is complete.

  (3)如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

三、偏向锁

  引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

1、偏向锁获取过程:

  (1)访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态。

  (2)如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。

  (3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。

  (4)如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。

  (5)执行同步代码。

2、偏向锁的释放:

  偏向锁的撤销在上述第四步骤中有提到偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

3、重量级锁、轻量级锁和偏向锁之间转换

 

                                        图 2.3三者的转换图

  该图主要是对上述内容的总结,如果对上述内容有较好的了解的话,该图应该很容易看懂。

四、其他优化 

1、适应性自旋(Adaptive Spinning):从轻量级锁获取的流程中我们知道当线程在获取轻量级锁的过程中执行CAS操作失败时,是要通过自旋来获取重量级锁的。问题在于,自旋是需要消耗CPU的,如果一直获取不到锁的话,那该线程就一直处在自旋状态,白白浪费CPU资源。解决这个问题最简单的办法就是指定自旋的次数,例如让其循环10次,如果还没获取到锁就进入阻塞状态。但是JDK采用了更聪明的方式——适应性自旋,简单来说就是线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。

2、锁粗化(Lock Coarsening):锁粗化的概念应该比较好理解,就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。举个例子:

package com.paddx.test.string;

public class StringBufferTest {
    StringBuffer stringBuffer = new StringBuffer();

    public void append(){
        stringBuffer.append("a");
        stringBuffer.append("b");
        stringBuffer.append("c");
    }
}

这里每次调用stringBuffer.append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。

3、锁消除(Lock Elimination):锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。看下面这段程序:

package com.paddx.test.concurrent;

public class SynchronizedTest02 {

    public static void main(String[] args) {
        SynchronizedTest02 test02 = new SynchronizedTest02();
        //启动预热
        for (int i = 0; i < 10000; i++) {
            i++;
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            test02.append("abc", "def");
        }
        System.out.println("Time=" + (System.currentTimeMillis() - start));
    }

    public void append(String str1, String str2) {
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }
}

虽然StringBuffer的append是一个同步方法,但是这段程序中的StringBuffer属于一个局部变量,并且不会从该方法中逃逸出去,所以其实这过程是线程安全的,可以将锁消除。下面是我本地执行的结果:

  为了尽量减少其他因素的影响,这里禁用了偏向锁(-XX:-UseBiasedLocking)。通过上面程序,可以看出消除锁以后性能还是有比较大提升的。

  注:可能JDK各个版本之间执行的结果不尽相同,我这里采用的JDK版本为1.6。

五、总结 

  本文重点介绍了JDk中采用轻量级锁和偏向锁等对Synchronized的优化,但是这两种锁也不是完全没缺点的,比如竞争比较激烈的时候,不但无法提升效率,反而会降低效率,因为多了一个锁升级的过程,这个时候就需要通过-XX:-UseBiasedLocking来禁用偏向锁。下面是这几种锁的对比:

优点

缺点

适用场景

偏向锁

加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。

如果线程间存在锁竞争,会带来额外的锁撤销的消耗。

适用于只有一个线程访问同步块场景。

轻量级锁

The thread does not block competition, improve the response speed of the program.

If still unable to get the spin lock contention threads consumes CPU.

The pursuit of response time.

Sync blocks performed very fast.

Heavyweight lock

Thread competition without the use of spin, does not consume CPU.

Thread is blocked, the response time is slow.

The pursuit of throughput.

Sync blocks long execution speed.

 

 

 

 

 

 

 

From:  https://www.cnblogs.com/paddix/p/5405678.html

 

Guess you like

Origin www.cnblogs.com/JonaLin/p/11490430.html