Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the influence of Java lock expansion process and consistent hash on lock expansion?

2025-02-21 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article shares with you about the expansion process of Java locks and the effect of consistent hashing on lock inflation. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.

1. Lock optimization

Before JDK6, the efficiency of synchronization through synchronized is very low. After the code block wrapped by synchronized is compiled by javac, monitorenter and monitorexit bytecode instructions will be added before and after the code block, and the method modified by synchronized will be marked with ACC_SYNCHRONIZED. No matter how it is expressed in the bytecode, the function and function are the same. If the thread wants to execute the synchronization code block or synchronization method, it first needs a competitive lock.

Synchronized ensures that at most one thread can compete for the lock at any one time, so what about the thread that does not compete for the lock?

Before JDK6, Java synchronized directly through OS-level mutexes (Mutex). The thread that could not get the lock was blocked and suspended until the thread holding the lock released the lock and then woke it up. This requires OS to frequently switch the thread from the user state to the kernel state. This switching process is very expensive. OS needs to pause the original thread and save data, wake up the new thread and recover the data, so synchronized is also called "heavyweight lock".

It is also for performance reasons that developers gradually abandon synchronized and join the arms of ReentrantLock.

After officials realized this problem, they took "efficient concurrency" as an important improvement project of JDK6. After many optimizations by the development team, the performance of synchronized has been maintained in the same order of magnitude as ReentrantLock. Although it is still a bit slow to lose, officials say that there is still room for optimization of synchronized in the future.

1.1. Lock elimination

When designing a class, code blocks are often locked in consideration of concurrency security issues.

But sometimes there is no multithreaded competition when the class is designed to be "thread-safe", so is there any reason to lock?

Lock elimination optimization benefits from the maturity of escape analysis technology, just-in-time compiler will scan the code at run time and eliminate locks that do not have shared data competition.

For example, if you instantiate a thread-safe class in a method (private to the stack memory thread), and the instance is neither passed to another method nor returned as an object (no escape occurs), JVM will unlock it.

In the following code, although the append () of StringBuffer is decorated by synchronized, there is no thread contention and the lock is eliminated.

Public String method () {StringBuffer sb = new StringBuffer (); sb.append ("1"); / / append () is sb.append modified by synchronized ("2"); return sb.toString ();} 1.2, lock coarsening

Due to the high cost of lock competition and release, if the lock is frequently contended and released in the code, JVM will optimize it to expand the scope of the lock appropriately.

The following code expands the scope of the lock outside the loop after coarsening using the synchronized,JVM lock within the loop.

Public void method () {for (int I = 0; I)

< 100; i++) { synchronized (this){ ... } }}1.3、自旋锁 当有多个线程在竞争同一把锁时,竞争失败的线程如何处理? 两种情况: 将线程挂起,锁释放后再将其唤醒。 线程不挂起,进行自旋,直到竞争成功。 如果锁竞争非常激烈,且短时间得不到释放,那么将线程挂起效率会更高,因为竞争失败的线程不断自旋会造成CPU空转,浪费性能。 如果锁竞争并不激烈,且锁会很快得到释放,那么自旋效率会更高。因为将线程挂起和唤醒是一个开销很大的操作。 自旋锁的优化是针对"锁竞争不激烈,且会很快释放"的场景,避免了OS频繁挂起和唤醒线程。 1.4、自适应自旋锁 当线程竞争锁失败时,自旋和挂起哪一种更高效? 当线程竞争锁失败时,会自旋10次,如果仍然竞争不到锁,说明锁竞争比较激烈,继续自旋会浪费性能,JVM就会将线程挂起。 在JDK6之前,自旋的次数通过JVM参数-XX:PreBlockSpin设置,但是开发者往往不知道该设置多少比较合适,于是在JDK6中,对其进行了优化,加入了"自适应自旋锁"。 自适应自旋锁的大致原理:线程如果自旋成功了,那么下次自旋的最大次数会增加,因为JVM认为既然上次成功了,那么这一次也很大概率会成功。 反之,如果很少会自旋成功,那么下次会减少自旋的次数甚至不自旋,避免CPU空转。 1.5、锁膨胀 除了上述几种优化外,JDK6加入了新型的锁机制,不直接采用OS级的"重量级锁",锁类型分为:偏向锁、轻量级锁、重量级锁。随着锁竞争的激烈程度不断膨胀,大大提升了竞争不太激烈的同步性能。 "synchronized锁的是对象,而非代码!" 每一个Java对象,在JVM中是存在对象头(Object Header)的,对象头中又分Mark Word和Klass Pointer,其中Mark Word就保存了对象的锁状态信息,其结构如下图所示: 无锁:初始状态 一个对象被实例化后,如果还没有被任何线程竞争锁,那么它就为无锁状态(01)。 偏向锁:单线程竞争 当线程A第一次竞争到锁时,通过CAS操作修改Mark Word中的偏向线程ID、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步。 轻量级锁:多线程竞争,但是任意时刻最多只有一个线程竞争 如果线程B再去竞争锁,发现偏向线程ID不是自己,那么偏向模式就会立刻不可用。即使两个线程不存在竞争关系(线程A已经释放,线程B再去获取),也会升级为轻量级锁(00)。 重量级锁:同一时刻多线程竞争 一旦轻量级锁CAS修改失败,说明存在多线程同时竞争锁,轻量级锁就不适用了,必须膨胀为重量级锁(10)。此时Mark Word存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程必须进入阻塞状态。 2、锁膨胀实战 说了这么多,理论终归是理论,不如实战一把来的直接。 通过编写一些多线程竞争代码,以及打印对象的头信息,来分析哪些情况下锁会膨胀,以及膨胀成哪种类型的锁。 2.1、jol工具 openjdk提供了jol工具,可以打印对象的内存布局信息,依赖如下: org.openjdk.jol jol-core 0.92.2、锁膨胀测试代码 程序启动时先sleep5秒是为了等待偏向锁系统启动。 编写一段锁逐步膨胀的测试代码,如下所示: public class LockTest { static class Lock{} public static void main(String[] args) { sleep(5000); Lock lock = new Lock(); System.err.println("无锁"); print(lock); synchronized (lock) { //main线程首次竞争锁,可偏向 System.err.println("偏向锁"); print(lock); } new Thread(()->

{synchronized (lock) {/ / Thread A competes, preferring thread ID instead of itself, upgrade to: lightweight lock System.err.println ("lightweight lock"); print (lock) }, "Thread-A"). Start (); sleep (2000); new Thread (()-> {synchronized (lock) {sleep (1000)) }}, "Thread-B") .start (); / / make sure thread B starts and acquires the lock, sleep 100ms sleep Synchronized (lock) {/ / main thread contention, thread B has not been released, multithread competition at the same time, upgraded to: heavyweight lock System.err.println ("heavyweight lock"); print (lock) }} static void print (Object o) {System.err.println ("= object information start. ="); System.out.println (ClassLayout.parseInstance (o). ToPrintable ()); / / jol asynchronous output to prevent print overlap, sleep1 seconds sleep (1000) System.err.println ("= end of object information... =");} static void sleep (long l) {try {Thread.sleep (l);} catch (InterruptedException e) {e.printStackTrace ();} 2.3, output analysis

After running, analyze the output information of the console. Paste the screenshot and write notes here:

No lock

Bias lock

Lightweight lock

Weight lock

The above is the process of gradual expansion of locks in JVM. In addition, locks do not support fallback and revocation.

2.4. Lock release

The biased lock will not be released actively, as long as there is no competition from other threads, it will always be biased towards the thread that holds the lock, so that in the future execution, there is no need for synchronization processing, saving synchronization overhead.

Public static void main (String [] args) {sleep (5000); Lock lock = new Lock (); synchronized (lock) {System.err.println ("Main thread contention lock for the first time"); print (lock);} System.out.println (); sleep (1000); System.err.println ("after synchronization code block exits") Print (lock);}

Both lightweight and heavyweight locks are released actively, and only lightweight locks are posted here.

Public static void main (String [] args) {sleep (5000); Lock lock = new Lock (); synchronized (lock) {/ / bias lock} new Thread (()-> {synchronized (lock) {System.err.println ("lightweight lock"); print (lock) }, "Thread-A"). Start (); sleep (5000); System.err.println ("after thread A releases the lock"); print (lock);}

The heavyweight lock is similar, so the test results will not be posted here.

3. The influence of consistent hash on lock expansion.

If an object has calculated a hash code, it should keep that value unchanged (highly recommended but not mandatory, because the user can overload the hashCode () method to return the hash code as he or she wishes).

In Java, if the class does not override hashCode (), it automatically inherits from Object::hashCode (). Object::hashCode () is a consistent hash, and as long as it is calculated once, the hash code is written to the object header and will never change.

Related to the specific hash algorithm, there are five hash algorithms in JVM, which are specified by parameter-XX:hashCode= [0 | 1 | 2 | 3 | 4].

As long as the object has calculated a consistent hash, the bias mode is set to 0, which means that the object lock can no longer be biased, and at least it will swell into a lightweight lock.

If an object lock encounters a computationally consistent hash request when it is in biased mode, the lightweight lock mode is skipped and directly inflated to a heavyweight lock.

After the lock is expanded to lightweight or heavyweight lock, the lock record pointer and heavy lock pointer in the thread stack frame are stored in Mark Word, and there is no place to save the hash code and GC age, so where is the information moved?

When upgrading to a lightweight lock, JVM creates a lock record (Lock Record) space in the stack frame of the current thread to store the Mark Word copy of the lock object, where the hash code and GC age are naturally saved, and the information is written back to the object header when the lock is released.

After upgrading to a heavyweight lock, the heavyweight lock pointer saved by Mark Word represents that the ObjectMonitor class of the heavyweight lock has a field to record the Mark Word in the unlocked state. After the lock is released, the information will be written back to the object header.

Code combat, skip bias lock, directly expand lightweight lock

Public static void main (String [] args) {sleep (5000); Lock lock = new Lock (); / / No override, consistent hash, invalid lock.hashCode (); synchronized (lock) {System.err.println ("it should be biased lock, but because the consistent hash has been calculated, it will directly inflate to lightweight lock") Print (lock);}}

When a request for consistent hash calculation is encountered in the process of biased lock, the biased mode is immediately revoked and expanded to a heavy lock.

Public static void main (String [] args) {sleep (5000); Lock lock = new Lock (); synchronized (lock) {/ / No override, consistent hash, invalid lock.hashCode after rewrite () System.err.println ("when a consistent hash calculation request is encountered during a biased lock, the biased pattern is immediately revoked and inflated to a heavyweight lock"); print (lock);}}

4. Lock performance test

Only a simple test has been done here, and the actual application environment is much more complex than the test environment.

Performance test of various types of locks under single thread:

Public class PerformanceTest {final static int TEST_COUNT = 100000000; static class Lock {} public static void main (String [] args) {sleep (5000); System.err.println ("performance testing of various types of locks"); Lock lock = new Lock (); long start; long end Start = System.currentTimeMillis (); for (int I = 0; I

< TEST_COUNT; i++) { } end = System.currentTimeMillis(); System.out.println("无锁:" + (end - start)); //偏向锁 biasedLock(lock); start = System.currentTimeMillis(); for (int i = 0; i < TEST_COUNT; i++) { synchronized (lock) {} } end = System.currentTimeMillis(); System.out.println("偏向锁耗时:" + (end - start)); //轻量级锁 lightweightLock(lock); start = System.currentTimeMillis(); for (int i = 0; i < TEST_COUNT; i++) { synchronized (lock) {} } end = System.currentTimeMillis(); System.out.println("轻量级锁耗时:" + (end - start)); //重量级锁 weightLock(lock); start = System.currentTimeMillis(); for (int i = 0; i < TEST_COUNT; i++) { synchronized (lock) {} } end = System.currentTimeMillis(); System.out.println("重量级锁耗时:" + (end - start)); } static void biasedLock(Object o){ synchronized (o){} } //将锁升级为轻量级 static void lightweightLock(Object o){ biasedLock(o); Thread thread = new Thread(() ->

{synchronized (o) {}}); thread.start (); try {thread.join ();} catch (InterruptedException e) {e.printStackTrace () }} / / upgrade locks to heavyweight static void weightLock (Object o) {lightweightLock (o); Thread T1 = new Thread (()-> {synchronized (o) {sleep (1000) }}); Thread T2 = new Thread (()-> {synchronized (o) {sleep (1000);}}); t1.start () T2.start (); try {t1.join (); t2.join ();} catch (InterruptedException e) {e.printStackTrace () }} static void sleep (long l) {try {Thread.sleep (l);} catch (InterruptedException e) {e.printStackTrace ();}

Performance testing of various types of locks

Unlocked: 6

Bias lock time: 252

Lightweight lock time: 2698

Heavy lock time: 1471

Because it is single-threaded and does not involve lock contention, heavyweight locks are faster than lightweight locks because there is no need for OS to schedule additional threads, threads do not need to suspend and wake up, and do not have to copy Mark Word.

In a multithreaded competitive environment, there is no doubt that the performance of heavyweight locks is degraded, as shown in the following tests:

Public static void main (String [] args) throws InterruptedException {System.err.println ("multithreaded testing"); Lock lock = new Lock (); long start; long end; / / lightweight lock lightweightLock (lock); start = System.currentTimeMillis (); for (int I = 0; I

< TEST_COUNT; i++) { synchronized (lock) {} } end = System.currentTimeMillis(); System.out.println("轻量级锁耗时:" + (end - start)); //重量级锁 weightLock(lock); Thread t1 = new Thread(() ->

{for (int I = 0; I)

< TEST_COUNT / 2; i++) { synchronized (lock) {} } }); Thread t2 = new Thread(() ->

{for (int I = 0; I < TEST_COUNT / 2; iTunes +) {synchronized (lock) {}}); t1.start (); t2.start (); start = System.currentTimeMillis (); t1.join (); t2.join (); end = System.currentTimeMillis () System.out.println ("heavyweight lock time:" + (end-start));}

Multithreaded testing

Lightweight lock time: 2581

Heavy lock time: 4460

Thank you for reading! This is the end of the article on "what is the impact of Java lock expansion and consistent hashing on lock expansion". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, you can share it for more people to see!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report