In the end how to ensure thread safety, summed up too well. .


First, the thread safety level

The issue of "thread safety" has been mentioned in previous blogs. Generally, we often say that a certain class is thread-safe and a certain class is not thread-safe. In fact, thread safety is not a "black and white" multiple-choice question.

According to the degree of "thread safety" from strong to weak, we can divide the data shared by various operations in the java language into the following five categories:Immutable, absolutely thread-safe, relatively thread-safe, thread-compatible, and thread-opposite.

1. Immutable

In the Java language, immutable objects must be thread-safe, and neither the object's method implementation nor the method's caller need to take any thread-safety measures. For example, the data modified by the final keyword cannot be modified and has the highest reliability.

2. Absolute thread safety

Absolute thread safety fully satisfies the definition of thread safety given by Brian GoetZ. This definition is actually very strict. It usually takes a lot of effort for a class to achieve "no matter what the runtime environment is, the caller does not need any additional synchronization measures". big price.

3. Relatively thread-safe

Relative thread safety means that a class is "thread safe" in the usual sense.

It needs to ensure that the individual operations on this object are thread-safe, and we do not need to make additional safeguards when calling, but for some consecutive calls in a specific order, additional synchronization methods may be required on the calling side to ensure the call correctness.

In the Java language, most thread-safe classes are relatively thread-safe, such as the collection guaranteed by the synchronizedCollection() method of Vector, HashTable, and Collections.

4. Thread Compatibility

Thread compatibility means that a class is not thread-safe in the usual sense.

Thread compatibility means that the object itself is not thread-safe, but the object can be safely used in a concurrent environment by using synchronization methods correctly on the calling side. Most of the classes in the Java API are thread compatible. Such as the collection classes ArrayList and HashMap corresponding to the previous Vector and HashTable.

5. Thread opposition

Thread opposition refers to code that cannot be used concurrently in a multithreaded environment regardless of whether the caller takes a synchronization error or not. Since the Java language is inherently multi-threaded, code that excludes multi-threading such as thread opposition rarely appears.

An example of a thread opposite is the supend() and resume() methods of the Thread class. If there are two threads holding a thread object at the same time, one tries to interrupt the thread and the other tries to resume the thread, if it is done concurrently, regardless of whether the call is synchronized or not, the target thread is at risk of deadlock. Because of this, these two methods have been deprecated.

Second, the implementation method of thread safety

Guaranteeing thread safety is classified according to whether synchronization means is required, and is divided into synchronization scheme and synchronization scheme without synchronization.

1. Mutual exclusion synchronization

Mutual exclusion synchronization is the most common concurrency correctness guarantee method. Synchronization means that when multiple threads concurrently access shared data, it is guaranteed that the shared data is only used by one thread at the same time (only one thread is operating the shared data at the same time). Mutual exclusion is a means to achieve synchronization, and critical sections, mutex and semaphore are the main ways to achieve mutual exclusion. Therefore, in these four words, mutual exclusion is the cause, and synchronization is the effect; mutual exclusion is the method, and synchronization is the purpose.

In java, the most basic means of mutual exclusion synchronization is the synchronized keyword. After the synchronized keyword is compiled, two bytecode qualities of monitorenter and monitorexit are formed before and after the synchronization block. Both bytecode instructions require A parameter of type reference to specify the object to lock and unlock.

In addition, ReentrantLock also achieves synchronization through mutual exclusion. In basic usage, ReentrantLock is very similar to synchronized, they both have the same thread reentrancy feature.

The main problem of mutual exclusion synchronization is the performance problem caused by thread blocking and wake-up, so this synchronization is also called blocking synchronization. In terms of the way to deal with the problem, mutual exclusion synchronization is a pessimistic concurrency strategy. It always believes that as long as correct synchronization measures (such as locking) are not taken, there will definitely be problems, no matter whether the shared data is really If there is a competition, it must be locked.

2. Non-blocking synchronization

With the development of the hardware instruction set, an optimistic concurrency strategy based on conflict detection has emerged. In layman's terms, the operation is performed first. If no other threads contend for the shared data, the operation is successful; if there is contention for the shared data, the If there is a conflict, then use other compensation measures. (The most common compensating error is to keep retrying until it succeeds.) Many implementations of this optimistic concurrency strategy do not need to suspend threads, so this synchronization is called non-blocking synchronization.

Non-blocking implementation of CAS (compareandswap): The CAS instruction requires three operands, which are the memory address (understood as the memory address of a variable in java, represented by V), the old expected value (represented by A) and the new value (indicated by B). When the CAS instruction executes, the processor updates the value at V with B if and only if the value at V matches the old expected value of A, otherwise it does not perform the update, but regardless of whether the value at V is updated , will return the old value of V, the above process is an atomic operation.

Disadvantages of CAS:

ABA problem: Because CAS needs to check whether the value has changed when operating the value, and if there is no change, update it, but a value was originally A, became B, and then became A, then when using CAS to check You will find that its value has not changed, but it has actually changed.

The solution to the ABA problem is to use the version number. Append the version number in front of the variable, and increment the version number by one each time the variable is updated, then ABA becomes 1A-2B-3C. The atomic package of the JDK provides a class AtomicStampedReference to solve the ABA problem. The role of the compareAndSet method of this class is to first check whether the current reference is equal to the expected reference, and whether the current flag is equal to the expected flag, and if all are equal, atomically set the reference and the value of the flag to the given updated value.

3. No need for synchronization scheme

To ensure thread safety, synchronization is not necessary, and there is no causal relationship between the two. Synchronization is only a means to ensure the correctness of shared data contention. If a method does not involve shared data, it naturally does not need any synchronization operation to ensure correctness, so there will be some code that is inherently thread-safe.

1) Reentrant code

Reentrant Code, also known as Pure Code, can be interrupted at any point in code execution and instead execute another piece of code, and after control is returned, the original program will not have any errors . All reentrant code is thread-safe, but not all thread-safe code is reentrant.

The characteristics of reentrant code are that it does not depend on the data stored on the heap and public system resources, the state quantities used are all passed in from parameters, and non-reentrant methods are not called.

(Analog: synchronized has the function of lock reentrancy, that is, when using synchronized, when a thread obtains an object lock, it can get the object lock again when it requests the object lock again)

2) Thread local storage

If the data required in a piece of code must be shared with other code, then see if the code sharing the data is guaranteed to execute in the same thread? If we can guarantee, we can limit the visibility of shared data to the same thread. This ensures that there is no data contention between threads without synchronization.

Applications that meet this feature are not uncommon. Most architectural patterns that use consumption queues (such as the "producer-consumer" pattern) will consume the product's consumption process in one thread as much as possible. One of the most important application examples is the processing method of "one request corresponds to one server thread (Thread-per-Request)" in the classic Web interaction model. The wide application of this processing method enables many Web server applications to use threads. Local storage to solve thread safety issues.

Original link:

Copyright statement: This article is an original article by CSDN blogger "LemmonTreelss" and follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement for reprinting.

Recommended recent hot articles:

1.1,000+ Java interview questions and answers (2021 latest version)

2.Don't use the if/else screen full of screens, try the strategy mode, it's delicious! !

3.What the hell! What is the new syntax for xx ≠ null in Java?

4.Spring Boot 2.6 is officially released, a wave of new features. .

5.The latest release of "Java Development Manual (Songshan Edition)", download quickly!

If you think it's good, don't forget to like + retweet!