This is the third day of my participation in the August More text Challenge. For details, see:August is more challenging

Synchronized’s performance was relatively low in JDK 1.5, but in subsequent iterations of optimization, synchronized’s performance has been improved in an unprecedented way. In the last article, we talked about how lock inflation has improved synchronized’s performance. However, it is only one of “many” synchronized performance optimizations, so let’s take a look at some of synchronized’s core optimizations.

Synchronized core optimization schemes mainly include the following four:

  1. Lock expansion
  2. Lock elimination
  3. Lock coarsening
  4. Adaptive spin lock

1. The lock

Let’s review the effects of lock inflation on synchronized performance. Lock inflation is the process by which synchronized moves from zero to biased, to lightweight, and finally to heavyweight locks. It is also called lock inflation.Prior to JDK 1.6, synchronized was a heavyweight lock, which meant that both the release and acquisition of a lock were converted from user mode to kernel mode, and the conversion was relatively inefficient. But with lock inflation, synchronized’s state is more unlocked, biased, and lightweight, and most scenarios do not require a user-mode to kernel mode transition when performing concurrent operations, thus dramatically improving synchronized performance.

PS: As for why the conversion from user mode to kernel mode is not required? Go to the article on lock inflation: The Lock Inflation Mechanism of Synchronized Optimizations.

2. Lock elimination

Many people know about lock inflation in synchronized, but they don’t know enough about the next three optimizations to miss out on interviews, so let’s take a look at them in this article.

Lock elimination refers to the situation in which the JVM eliminates the synchronization lock to which a piece of code belongs if it cannot detect the possibility that the piece of code is being shared or contested, thereby improving the performance of the application.

Lock cancellation is based on data supported by escape analysis, such as the append() method of StringBuffer or the add() method of Vector. It can be done in many cases, such as this code:

public String method(a) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < 10; i++) {
        sb.append("i:" + i);
    }
    return sb.toString();
}
Copy the code

The compiled bytecode of the above code looks like this:As we can see from the above results, the threadsafe, locked StringBuffer object we wrote before is replaced with an unlocked, unsafe StringBuilder object after bytecode generation, because the StringBuffer variable is a local variable. It does not escape from the method, so we can use lock elimination (without locking) to speed up the program.

3. Lock coarsening

Lock coarsening means that multiple consecutive lock and unlock operations are connected together to expand into a lock with a larger range.

I’ve only heard that lock “thinning” can improve the performance of a program, that is, making the lock as narrow as possible so that the thread waiting to acquire the lock can acquire it earlier in a lock race, but how does lock coarsening improve performance?

Yes, the idea of lock refinement is true in most cases, but a series of consecutive locking and unlocking operations can also result in unnecessary performance overhead that affects the performance of a program, such as this code:

public String method(a) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 10; i++) {
        // Pseudo code: lock operation
        sb.append("i:" + i);
        // Pseudo-code: unlock operation
    }
    return sb.toString();
}
Copy the code

Compiler optimizations are not taken into account here. If you define a lock in a for loop, the scope of the lock is small, but the performance is poor because each for loop requires locking and releasing the lock. However, if we add a lock directly to the for loop, the performance of this code for the same object will be much improved, as shown in the pseudocode below:

public String method(a) {
    StringBuilder sb = new StringBuilder();
    // Pseudo code: lock operation
    for (int i = 0; i < 10; i++) {
        sb.append("i:" + i);
    }
    // Pseudo-code: unlock operation
    return sb.toString();
}
Copy the code

Lock coarsening: If a series of lock and unlock operations are detected on the same object, these operations are combined into a larger lock to improve program execution efficiency.

4. Adaptive spin lock

Spin-locking is a method of attempting to acquire a lock by looping through itself. The pseudo-code implementation is as follows:

// Try to acquire the lock
while(! isLock()){ }Copy the code

The advantage of spin-locking is that it avoids suspending and resuming some threads, because both suspending and resuming threads need to move from user mode to kernel mode, which is a slow process, so the performance overhead of suspending and resuming threads can be avoided to some extent by spin-locking.

However, if the spin does not acquire the lock for a long time, then there is a waste of resources, so we usually give the spin a fixed value to avoid the performance overhead of spinning all the time. Synchronized, however, is more “intelligent” in terms of its spin locks, which are adaptive spin locks, like the manual tricycles we’ve been driving, but after JDK 1.6, our “car”, Suddenly it’s a Lamborghini with automatic transmission.Adaptive spin locking means,The number of spins a thread has is no longer a fixed value, but a dynamically changing value that determines the number of spins based on the state of the lock acquired by the previous spin. Such as capturing the last through the spin lock, so this time by the spin is likely to get into the lock, so this time the number of spin will be a little more, and if the last time failed to get through the spin lock, so the spin may get less than lock, so in order to avoid the waste of resources, will loop or loop not less, in order to improve the execution efficiency. In short,If the thread spins successfully, the next spin will be more, if it fails, the next spin will be less.

conclusion

In this paper, we introduce four synchronized optimization schemes, among which lock expansion and adaptive spin lock are the optimization implementation of synchronized keyword itself, while lock elimination and lock coarsening are the optimization schemes provided by JVM virtual machine for synchronized. These optimizations eventually led to significant improvements in synchronized’s performance and gave it a place in concurrent programming.

Reference & thanks

www.cnblogs.com/aspirant/p/…

zhuanlan.zhihu.com/p/29866981

tech.meituan.com/2018/11/15/java-lock.html

Recommended articles in this series

  1. Concurrency Lesson 1: Thread in Detail
  2. What’s the difference between user threads and daemon threads in Java?
  3. Get a deeper understanding of ThreadPool
  4. 7 ways to create a thread pool, highly recommended…
  5. How far has pooling technology reached? I was surprised to see the comparison between threads and thread pools!
  6. Thread synchronization and locking in concurrency
  7. Synchronized = “this” and “class”
  8. The difference between volatile and synchronized
  9. Are lightweight locks faster than weight locks?
  10. How can terminating a thread like this lead to service downtime?
  11. 5 Solutions to SimpleDateFormat Thread Insecurity!
  12. ThreadLocal doesn’t work? Then you are useless!
  13. ThreadLocal memory overflow code demonstration and cause analysis!
  14. Semaphore confessions: I was right to use the current limiter!
  15. CountDownLatch: Don’t wave. Wait till you’re all together!
  16. CyclicBarrier: When all the drivers are ready, they can start.
  17. Synchronized optimization means lock expansion mechanism!

Follow the public number “Java Chinese community” for more interesting and informative Java concurrent articles.