Synchronization and optimization of Java multithreading

Synchronized and synchronous blocking

synchronizedIt is the synchronization and locking mechanism provided by jvm, which corresponds to AbstractQueuedSynchronizerthe concurrency-based component provided by JUC at the jdk level. synchronizedProvided is mutual exclusion synchronization. Mutual exclusion synchronization means that when multiple threads access shared data concurrently, it is guaranteed that only one thread accesses the shared data at the same time.
In the jvm, after the synchronizedmodified code block is compiled by javac, a monitorenter and monitorexit bytecode instructions will be generated before and after the code block. Both bytecodes require a parameter of reference type to specify the lock and unlock. object. If synchronizedthe object parameter is specified, the reference is the reference of the object. If it is not specified manually, then synchronizedthe corresponding object instance or Class object is taken as the lock object according to whether the modification is an instance method or a class method.
It is worth mentioning that in java Objectclasses have two methods wait()and notify()( notifyAll()similar notify()) and synchronization locks. As for why these two methods are placed in the Objectclass, the reasons are as follows: wait()the semantics of the method is to make the current thread (the thread calling wait()the method) wait until it is notify()awakened by the method. The current thread must own the lock of the object. wait()After calling the method, the current thread will release the lock of the synchronization object. Therefore, the wait()method must be inside the synchronizeddecorated code block (which definitely acquires a synchronization lock), and since any Java object can act as an object lock, these two methods need to be placed Objectin it.
Java threads are mapped to the native threads of the operating system. If you want to block or wake up a thread, you need to enter the kernel to complete. This state transition from user state to kernel state requires a lot of processor time, so it synchronizedis A very resource intensive operation in Java. Before JDK1.5,synchronizedCompared with the JDK-based implementation ReentrantLock, the performance is much lower. In JDK1.6 and later versions, synchronizedmany optimization measures for locks have been implemented, and a large part of these optimizations are ReentrantLocksimilar to the implementation ideas of .

Non-blocking synchronization with CAS

Blocking synchronization often involves locking and unlocking, which means that switching between user mode and kernel mode is very resource-intensive. For this reason, an optimistic concurrency strategy based on conflict detection emerges as the times require. The core idea of ​​this strategy is to operate on the data first. If there is no other thread contending for the data, the operation is considered successful; otherwise, other methods are used to Ensure that the data operation is successful (for example: keep retrying until it succeeds). This concurrency strategy does not need to suspend threads, so it is called non-blocking synchronization.
CAS is short for CompareAndSwap, which requires three operands, which are the memory location, the expected value, and the updated value. When the CAS instruction is executed, the processor first judges whether the value of the memory location is equal to the expected value. If it is equal, it updates the expected value to the updated value; otherwise, it is considered that other threads have modified the value of this memory location, and the update operation will not allow. The CAS operation returns the expected value regardless of whether the update operation occurs or not. The CAS operation is an atomic operation .
CAS is very suitable for non-blocking synchronization. For example, to add 1 to an int value i, the idea of ​​using CAS is to first determine whether the value of the memory location of variable i has been modified. If not, update the value of this location to i+1; otherwise, it is considered that other threads have modified this value, and the previous operation can be called again to try to update until the update is successful. Because, when judging the value of the memory location, the value of the memory location needs to be obtained first. In Java, in order to ensure the visibility of this variable, it needs to volatilebe modified with keywords. After JDK1.5, Java programs provide sun.misc.Unsafeclasses to implement the encapsulation of basic types of CAS operations, and JUC components are implemented based on this class. Take a java.util.concurrent.atomic.AtomicIntegerclass as an example, this class wraps an instance variable value:

private volatile int value;

The source code to implement auto-increment for this variable is as follows:

/**
  * Atomically increments by one the current value.
  *
  * @return the updated value
  */
  public final int incrementAndGet() {
      return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
  }

The Unsafe#getAndAddInt()implementation of which is as follows:

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

Here var2 is the value of the memory address, and var5 is the expected value. When the compareAndSwapInt() method returns true, it means that the CAS operation is successful, otherwise retry until it is successful. From here, three disadvantages of CAS operation can be found:

  1. The ABA problem is that if the value of the variable is changed by other threads but is updated to the expected value before the current thread calls the CAS instruction, the CAS instruction will still think that the value of the inner position has not been modified, which is consistent with the original intention of the CAS design. does not match. However, this problem can be solved by introducing a version number;
  2. efficiency issues. If there are a large number of threads modifying the value of the specified variable, the probability of a successful CAS operation is very low, and retrying all the time will reduce the performance of the virtual machine;
  3. CAS is still only able to synchronize a single variable, not synchronizeda piece of code like that.
    Here, unsafe is Unsafean instance. This class is not a class provided to user programs. Most of the encapsulated JNI methods in it are. Programmers generally do not need to use this class, but only need to know that it is the encapsulation of CAS operations in Java. .

lock optimization

The corresponding synchronization locks described above are synchronizedrelatively heavyweight. For this reason, after JDK1.5, developers have implemented various lock optimization techniques, including adaptive spin locks, lock elimination, lock coarsening, lightweight locks and biased locks. Wait. Note that these locks are all optimizations at the virtual machine level, which can be considered as optimizations for the synchronizedcorresponding bytecodes.

Spinlocks and Adaptive Spinlocks

Spin lock is an optimization technique obtained by virtual machine developers' statistical analysis of shared data, that is, in most cases, the lock state of shared data will only last for a short period of time, and when other threads are waiting for the lock , it is not worth suspending and resuming threads for this waiting time. In order to let the thread wait for a short time without suspending, you can let the thread execute a busy loop (spin), which is a spin lock. Note that spin locks need to consume CPU resources. If the lock time of shared data happens to be very long, then the performance of spin locks will decrease. Adaptive spin lock is an optimization of spin lock, which can dynamically adjust the spin time to avoid blind waiting.

lock removal

Lock elimination is an optimization at the javac level. When some programmers write synchronizedcode decorated with or call, but it is detected that there is no shared data race, javac will optimize this part of the code to eliminate redundant synchronization instructions.

Chain coarsening

In principle, when writing a program, the smaller the synchronization block is, the better. However, when a series of continuous operations are repeatedly locking and unlocking the same object, it is a waste of resources. At this time, the scope of the lock is Expanding to the entire sequence of operations is beneficial for saving resources occupied by repeated locking and unlocking. Both lock elimination and lock coarsening techniques are smart optimizations of javac or jvm.

Lightweight lock

Lightweight locks reduce the performance consumption of traditional heavyweight locks using operating system mutexes without multi-thread competition. The basis for the implementation of lightweight locks is: "Most of the time, there is no competition in the entire synchronization cycle." When there is no competition, the use of lightweight locks does not require the use of the mutex of the operating system. save resources. However, when there is contention, lightweight locks will have both the CAS overhead and the overhead of traditional heavyweight lock operations, resulting in lower performance.
The implementation of lightweight locks is divided into two processes: locking and unlocking:

  1. Locking: JVM creates a space called Lock Record in the stack frame of the current thread to store the object header information of the lock object. After the storage is successful, the virtual machine will use the CAS operation to try to update the object header of the object to be locked to a pointer to the lock record. If the operation is successful, then the current thread is considered to own the lock of the object. At this time, the object header's The lock flag will be updated to "00" - a lightweight lock; if it fails, there are two cases: the thread already owns the lock of this object or there is a competition for shared data, and it is only necessary to check whether it already owns the lock. Whether the Mark Word of the target object points to the current stack frame, if so, enter the synchronization fast; otherwise, it is considered that there is a competition, and the lightweight lock is updated to a heavyweight lock, and then the operation is the same as the traditional heavyweight lock;
  2. Unlocking: Unlocking is also implemented based on CAS operations. When unlocking, if the Mark Word of the object still points to the current thread stack frame, use the CAS operation to update the Mark Word of the object; if successful, the entire synchronization process is completed; otherwise, it means that other threads have tried to acquire the lock, then While releasing the lock, wake up the suspended thread .

Bias lock

Biased locks are a more radical approach than lightweight locks: the entire synchronization is eliminated in the absence of competition. Biased locks mean that the lock will be biased towards the first thread to acquire it, if the next During the execution of the lock, the lock is not acquired by other threads, then the thread holding the biased lock will never need to synchronize. The virtual machine starts the biased lock through the -XX:+UseBiasedLocking parameter. When the lock object is acquired by the thread for the first time, the virtual machine will set the flag bit in the object header to "01" - biased lock mode. At the same time, use the CAS operation to record the ID of the thread that acquired the lock in the Mark Word of the object. If the CAS operation is successful, every time the thread holding the biased lock enters the synchronization block related to the lock, the virtual machine can no longer Do any synchronization; the biased lock ends when another thread tries to acquire the lock. At this time, according to whether the lock object is currently in the locked state, after revoking the bias, it returns to the unlocked or lightweight locked state, and subsequent synchronization operations are performed as the lightweight locks described above.


Reference: "In-depth Understanding of the Java Virtual Machine" Second Edition, Zhou Zhiming

Guess you like

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