Thread Safety and lock optimization

Source: Internet
Author: User
Tags array length cas instance method mutex thread class stringbuffer

1. Preface

The previous article recorded the relationship between Java's memory model and thread, which has been addressed by the memory model to the problem of thread safety. This article will be a specific description of thread safety, the implementation of the lock to explore, to understand the principle of the lock is what, to better use the lock, to troubleshoot related issues.

2. Thread Safety

Brian Goetz, author of the Java Concurrency in practice, has a more appropriate definition of thread safety: When multiple threads access an object, there is no need for additional synchronization if you do not have to consider the scheduling and alternation of these threads in the runtime environment. Or, if you call the method to do any other coordinated operation, the object's behavior can get the correct result, then the object is thread-safe. This definition requires that the code itself encapsulates all the necessary guarantees of correctness, such as mutual exclusion synchronization, so that the caller does not have to worry about the threading problem, and does not need to take any action to ensure the correct invocation of multithreading.

2.1 Thread Safety in Java

What is the specific embodiment of thread safety? What operations are thread-safe? According to the "security degree" of thread-safe ordering from strong to weak, we can divide the data shared by various operations in the Java language into the following 5 classes: immutable, absolute thread-safe, relative thread-safe, thread-compatible, and thread-antagonistic.

2.1.1 Not variable

In the Java language, immutable objects must be thread-safe and do not require any thread-safety safeguards. As long as an immutable object is built correctly (without the case of this reference escaping), the visible state of its outside will never change and will never see it in an inconsistent state in multiple threads.

In the Java language, if the shared data is a basic data type, only the final keyword is required, and if it is an object, the object's behavior is guaranteed to have no effect on its state. String, for example, produces a new string class, regardless of what modification method is called, and the original string value remains unchanged. There are many ways to ensure that an object's behavior does not affect its own state, and the simplest is to declare the variable that will change to final.

Java conforms to this definition of the type, in addition to string, there is a subset of number of the subclass, but the atomic class Atomicinteger and Atomiclong concurrency immutable. What is this for? Personal understanding is to avoid repeatedly creating objects, its internal implementation through volatile to ensure visibility, in addition, through the CAS operation to ensure the update action of thread safety, do not need to design immutable type. Its own meaning is variable, need to constantly self-increase.

2.1.2 Absolute Thread Safety

Absolute thread safety needs to fully meet the thread-safe definition given by Brian Goetz, a definition that is very strict, and a class that has to be "no matter what the operating environment, the caller does not need any additional synchronization measures", which costs a great price. In the Java API you label yourself as a thread-safe class, most of which are not absolutely thread-safe. Like the vector class, this is a thread-safe container whose add, get, and size methods are synchronized decorated, albeit inefficient. This does not mean that this is safe:

Suppose a thread is constantly add, multiple threads are constantly remove,for (int i = 0; i < vector.size (); i++) {Vector.remove (i)}. This could result in the decision that after the size method has been passed, the remove operation has just been removed, and the other thread has already deleted the value, and the deletion of the thread eventually throws an exception.

This example shows that the size () and remove () operations must be locked at the same time, and the atomicity of a single operation does not guarantee that the combined operation is thread safe when it is actually used.

2.1.3 Relative Thread Safety

Relative thread safety is usually thread-safe, and it is important to ensure that the individual operations on this object are thread-safe. This does not require additional safeguards for a single operation, but for sequential invocations of some order, synchronization may be required to ensure security. The 2.1.2 cited example is the proof.

2.1.4 Thread Compatibility

Thread compatibility refers to the fact that the object itself is not thread-safe, but it is possible to ensure that the object is safe to use in a concurrent environment by using synchronous means correctly at the caller, which is what we normally call a class that is not thread-safe. Most of the classes in the Java API are thread-compatible, such as Arraylist,hashmap.

2.1.5 Thread Antagonism

Thread antagonism refers to code that cannot be used concurrently in a multithreaded environment, regardless of whether the caller takes synchronization measures. Java is inherently multi-threaded, and thread-antagonistic code rarely appears, often harmful, and avoided as much as possible.

A contradictory example is the suspend () and the Resume () method in the thread class. If an attempt is interrupted, an attempt is resumed, concurrent execution, regardless of whether it is synchronized, the target thread is at risk of having a deadlock. The thread that suspend interrupts is the thread that will execute the resume, which is sure to create a deadlock. It is for this reason that the suspend and resume methods were discarded by the JDK. Common thread-antagonistic operations include System.setin (), System.setout (), and System.runfinalizersonexit ();

3. Thread-Safe Implementation Method 3.1 mutex synchronization

Mutual exclusion synchronization is a common guarantee method of concurrency correctness. Synchronization is the guarantee that shared data is used by only one or more threads at the same time when multiple threads concurrently access shared data. And the mutual exclusion is a means of realizing synchronization, the critical area, mutual exclusion and semaphore are the main mutually exclusive implementation modes. Therefore, the mutex is the result of the synchronization.

In Java, the most basic mutually exclusive synchronization means is the Synchronized keyword, the keyword after compiling, will be in the synchronization block before and after the formation of the Monitorenter and monitorexit the two bytecode instructions, All two instructions require a parameter of type reference. If synchronized (object), which indicates the objects, is the object, and if it is a decorated instance method or a class method, it uses the corresponding instance or classes object as the lock object. According to the specification requirements, when executing the monitorenter instruction, the first attempt to acquire the lock, if the object is not locked, or the current thread has a lock, the lock counter plus 1, the relative Monitorexit 1, when the counter is 0, the lock release. If a lock failure is acquired, a blocking wait is required until the object lock is freed by another thread.

There are 2 places to be aware of. One is that the Synchronized sync block can be re-entered on the same thread, without having to lock himself up. In addition, the synchronization block blocks the other threads coming in before the entered thread finishes executing. Another article said that threads are mapped to the native thread of the operating system, and if you want to block or wake up a thread, you need the operating system to do it, which requires a transition from the user state to the kernel state, so the state transition takes a lot of processor time. For a simple synchronization block of code blocks, such as the Get or set method of the synchronized adornment, the state transition may take longer than the time of the user code. So synchronized is a heavy-weight operation. This operation is only used if necessary. Virtual opportunities are optimized, such as adding a spin-wait process before notifying the operating system to block threads, to avoid frequent switching to the kernel state.

In addition to synchronized, You can also use the reentrant lock Reentrantlock in the Java.util.concurrent package for synchronization, which is similar to synchronized, but somewhat different from the code, one is the mutex on the API, and the lock and unlock methods need to match the try The/finally statement is implemented, and the other is a mutex in the native syntax layer. But Reentrantlock has a few more features than synchronized: Waiting can be interrupted, fair locks can be achieved, and locks can bind multiple conditions.

Waiting for interruptible means that the thread that is currently holding the lock does not release the lock for a long time, and the waiting thread can choose to discard the wait and handle other things.

A fair lock is when multiple threads are waiting for the same lock, the lock must be acquired one time in the order in which the lock is requested, and an unfair lock does not guarantee that any thread that waits for a lock is given a chance to acquire a lock when the lock is released. Synchronized is non-fair, reentrantlock default is also unfair, need to set.

Lock binding multiple conditions refer to a Reentrantlock object that can bind multiple condition objects at the same time.

JDK5, the performance of synchronized in the multi-threaded down quickly, but not to say reentrantlock performance is good. There are many optimizations for locking in JDK6, and the performance gap is less dramatic.

3.2 Non-blocking synchronization

The main problem with mutex synchronization is the performance problem of thread blocking and wake-up, which is blocking synchronization. In dealing with the problem, the mutex synchronization is a pessimistic concurrency strategy, always think that as long as not to do the correct synchronization measures, there will be problems. With the development of the hardware instruction set, we have another option: optimistic concurrency strategy based on conflict detection. The simple thing is to do it first, and if no other thread is competing for shared data, the operation succeeds, and if there is a conflict, take another step. This strategy does not need to suspend threads, called non-blocking synchronizations.

This need for hardware instruction set development is due to the need for operational and conflict detection of the two steps are atomic, if the use of mutual exclusion synchronization guarantee, then there is no sense, so need hardware support. The hardware needs to be guaranteed to appear to require more than one instruction to complete, such as:

Test and set (Test-and-set),

Get and Add (fetch-and-increment),

Swap (Swap),

Compare Exchange (Compare-and-swap,cas),

Load link/Condition Store (LOAD-LINKED/STORE-CONDITIONAL,LL/SC).

The first 3 were among the majority of instruction sets in 20th century, and the latter 2 were new to modern processors. The IA64, x86 instruction set has the CMPXCHG instruction to complete the CAS function, Sparc-tso also has the Casa instruction implementation, in the arm and PowerPC architecture, you need to use a pair of Idrex/strex instructions to complete the LL/SC function.

The CAS directive requires 3 operands, the memory location, the old expected value, and the new value, and if the old expected value is expected, it will be replaced with the new value, and the old value is returned, regardless of success.

JDK5 provides CAS operations, in the Sun.misc.Unsafe class, several methods are provided, the virtual machine to its internal special processing, the results of the immediate compilation is a platform-related CAS directive, there is no method call process, or can be considered unconditional inline in. The unsafe class is not provided to the user program, and the code restricts access to the class that is loaded by the bootloader. Therefore, it is not used as a reflection method, but indirectly through API.

CAS looks good, but there are still some problems that can't be solved, such as the ABA problem. If a thread reads a, the next time it reads A, is it possible to say that the field has not changed? Definitely not, just like the ABA, another thread changed to B after the change back, can not be judged. The workaround is to add a version number, but it might be more efficient to use a traditional mutex synchronization.

3.3 No synchronization scheme

To ensure thread safety, you do not necessarily need to synchronize. Synchronization is just the right way to ensure that the shared data is competing, and if a method does not involve sharing data, it does not need to be synchronized. Some of the code is inherently thread-safe, and two of these are described below.

Reentrant code: This code is also called Pure code, interrupts it at any point in the execution of the Code, and goes on to execute another piece of code (including the recursive call itself), and the original program will not have an error after control returns. With respect to thread safety, reentrant is a more basic feature that guarantees thread safety. Reentrant code has some common characteristics: such as not relying on the data stored on the heap and the common resource system, the amount of state used is passed by the parameter, not the non-reentrant method, and so on. The simple way to determine whether reentrant is to have a method whose return result is predictable, and to return the same result if the same data is entered, it satisfies the requirement of reentrant, and of course it is thread-safe.

Thread-Local Storage: If the data for a piece of code must be shared with other code, it depends on whether the code for the shared data is executed in the same thread. If you can guarantee that the shared data is used in one thread, it is naturally thread-safe. For example, a Web service, a request corresponding to a thread. In Java, you can also implement a data-bound thread through a Threadlocalmap object that only this thread can access.

4. Lock optimization

Previously said JDK5 's synchronized performance and reentrant compared to the gap is obvious, but in JDK6 will not be too bad, this is to achieve a variety of lock optimization technology. such as adaptive spin, lock elimination, lock coarsening, lightweight locks and biased locks. These nouns are often heard, but not well understood, and are described below.

4.1 Spin lock and adaptive spin

The biggest problem mentioned in the previous mutex synchronization is the implementation of blocking, the operation of suspending and resuming the thread needs to be completed in the kernel state, which brings great pressure to the concurrency of the operating system. At the same time, the development team noticed that in many applications, the locked state of the shared data lasted only a short period of time, and it was not worthwhile to suspend and resume the thread for this period of time. So we can have the thread wait a little longer without abandoning the processor's execution time, and see if the thread holding the lock releases the lock quickly. The implementation is to have the thread perform a loop (spin), which is the spin lock.

The spin lock is available in the JDK1.4, but it needs to be turned on using the-xx:+usespinning parameter, and JDK6 is on by default. Spin waiting is not a substitute for blocking, although the spin itself avoids the overhead of thread switching, but it consumes the processor time and wastes resources. So the spin waiting time needs to be limited, if more than the limit of the number of times you need to use the traditional way to hang up the thread. The number of spins is the default value of 10 times, which can be changed by-xx:preblockspin.

The adaptive Spin lock is introduced in the JDK6, which means that the spin time is no longer fixed and is determined by the previous spin time in the same lock and the state of the lock holder. If, on the same lock object, the spin wait has just been successfully acquired and the thread holding the lock is running, the virtual machine will assume that the spin may succeed again and wait longer, such as 100 loops. If the spin success is very small, then you may omit the spin step, directly block, to avoid waste of processor resources.

4.2 Lock Removal

Lock elimination refers to the instant compiler runtime of a virtual machine, when it is found that some synchronized code cannot have competitive data, and these synchronized locks are canceled. Lock elimination is the main judgment based on the data of the escape analysis, if a piece of code, all the data on the heap will not escape to be accessed by other threads, then they can be treated as data on the stack, considered to be thread-private, synchronous lock is naturally unnecessary.

Here is a question, developers should be very clear whether the variable escape, to the variable will not escape, how to add synchronization measures? The answer is that many of the synchronization measures are not added by the programmer, and the synchronization code is more prevalent in Java than most people think. For example, S1+S2+S3 three strings added, we all know that the string is an impossible variable, JDK5 before the conversion to StringBuffer for continuous append operation. The JDK5 will then be converted into StringBuilder append operations. For StringBuffer, each time the append is locked, but this object does not escape, so the lock will be eliminated.

4.3 Lock coarsening

In principle, when we write synchronous code, it is recommended that the scope of the synchronization block is limited to a minimum, which makes it less likely to occupy the lock. However, if a series of successive operations are repeatedly locked and unlocked for the same object, or even the lock operation appears in the loop body, then no thread contention for frequent synchronization can lead to unnecessary performance loss. For example, previously said StringBuffer continuous Append method, lock coarsening can be locked before the first append operation, the last append unlocked.

4.4 Lightweight Lock

JDK6 adds a new lock mechanism, which is lightweight relative to traditional locks implemented using operating system mutexes. Lightweight locks are not used in place of heavyweight locks, and are intended to reduce the performance cost of traditional heavyweight locks using operating system mutexes without multi-threading competition.

To understand the lightweight lock, and the principle behind the bias lock, first understand the memory layout of the virtual machine object-primarily the contents of the object header part of the objects head. The object header is divided into two parts, the first part is used to store the object's own runtime data, such as HASHCODE,GC age, which is called Mark Word, it is the key to implement lightweight and biased lock. The other part is used to store pointers to the class data structure of the method area object, and if it is an array object, there is an additional portion of the user storage array length.

Mark Word is designed as a non-fixed data structure that re-uses its own storage space based on the state of the object. For example: In an unlocked state in a 32-bit virtual machine object, Mark Word has 25 bits in 32bit space for the hashcode,4-bit storage generational age, 2 bits for storing the lock flag bit, and 1 bits fixed to 0. The status meaning and specific values of the lock flags are as follows:

When entering the synchronization block, if the synchronization object is not locked (01 state), the virtual machine first creates a space named lock record in the stack frame of the current thread, which is used to store the lock object's current copy of Mark Word, called displaced mark Word. The virtual machine will then use the CAS action to attempt to update the object's mark Word to a pointer to the lock record. If the update succeeds, the thread has a lock on the object, and the object's mark Word's lock flag bit 00 indicates that the object lock is in a lightweight lock state. Specific steps such as:

If this update fails, it checks to see if the object's mark word points to the stack frame of the current thread, and if it means that the thread has acquired the lock on the object, it can go straight to the synchronization code block, or else the lock object has been preempted by another thread. If more than two threads contend for a lock, the lightweight lock is no longer valid, it expands into a heavyweight lock, and the lock flag State becomes 10,mark Word stores a pointer to a heavyweight lock (mutex), and the thread that waits for the lock will go into a blocking state.

The unlocking process also replaces the current mark Word with a successful replacement, which fails to indicate that the thread has tried to acquire the lock and wakes the suspended threads while releasing.

The lightweight lock can boost program synchronization performance based on the empirical data that "there is no competition for most locks over the entire synchronization cycle." If there is no competition, the lightweight lock uses CAS operations and avoids the overhead of using mutexes, but if there is a lock contention, there is additional CAS operation in addition to the cost of the mutex, so the lightweight lock will be slower than the heavyweight lock in a competitive situation.

4.5 Bias Lock

This is also a lock optimization introduced by JDK6, in order to eliminate the data in the non-competitive situation of synchronization primitives, and further improve the performance of the program.

Lightweight locking is the use of CAS operations in the absence of competition to eliminate the mutex used by synchronization, then the bias Lock is no competition in the case of the entire synchronization eliminated, even the CAS operation is not done.

A bias lock means that the lock will be biased towards the first thread that gets it, and if the lock is not fetched by another thread during the next execution, the thread that holds the biased lock never needs to be synchronized. The JDK6 uses-xx:+usebiasedlocking to enable the biased lock, which is the default value. Then when the lock object is fetched by the thread for the first time, the virtual machine sets the flag in the object header to 01, which is the bias mode. At the same time using the CAS operation to get the thread ID of the lock recorded in the object's Mark Word, it is not necessary to perform any synchronization operations (Locking, unlocking, and update to mark Word) each time you enter the lock's synchronization block.

When another thread attempts to acquire this lock, the bias mode ends. Depending on whether the object is currently in a locked state, the undo bias reverts to the unlocked or lightweight lock state, and the subsequent synchronization operation is performed as a lightweight lock.

Biased locking can improve the performance of the program with synchronous but no competition, not always beneficial to the program, if most of the locks in the program are always accessed by multiple threads, bias mode is redundant, disabling may improve performance.

Thread Safety and lock optimization

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.