ThreadLocal, Volatile, synchronized, Atomic keyword literacy of concurrent programming

foreword

Regarding the four keywords of ThreadLocal, Volatile, synchronized, and Atomic, I would like to mention that everyone must be thinking of solving the problem of resource sharing in a multi-threaded concurrent environment, but I want to elaborate on the characteristics, differences, and applications of each one. Scenarios, internal implementations, etc., may be ambiguous, and it is impossible to say why. Therefore, this article will explain the functions, characteristics and implementation of these keywords.

1、Atomic

effect

For atomic operation classes, Java's concurrent packages mainly provide us with the following common ones:

AtomicInteger、

AtomicLong、

AtomicBoolean、

AtomicReference<T> 

For the atomic operation class, the biggest feature is that when multiple threads operate the same resource concurrently, the Lock-Free algorithm is used to replace the lock, which has low overhead and high speed . The atomicity of the operation is guaranteed.

What is atomicity? For example, an operation i++; in fact, these are three atomic operations, first read the value of i, then modify (+1), and finally write to i.

So use Atomic atomic operands, such as: i++; then it will allow other threads to operate on it only after this step is completed.

And this implementation is determined by the Lock-Free+ atomic operation instruction 

Such as: 
In the AtomicInteger class:
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

As for the Lock-Free algorithm, it is a new strategy to replace locks to ensure the integrity of resources during concurrency. The implementation of Lock-Free has three steps:

1. Loop (for(;;), while) 
2. CAS (CompareAndSet) 
3. Rollback (return, break)

usage

For example, when multiple threads operate a count variable, count can be defined as AtomicInteger, as follows:

public class Counter {
    private AtomicInteger count = new AtomicInteger();

    public int getCount() {
        return count.get();
    }

    public void increment() {
        count.incrementAndGet();
    }
}

In each thread, the count is incremented by increment(), or some other operation. In this way, each thread will access a safe and complete count.

Internal implementation

Using Lock-Free algorithm instead of lock + atomic operation instructions to achieve security, integrity and consistency of resources under concurrent conditions

2、Volatile

effect

Volatile can be seen as a lightweight synchronized , which can guarantee the "visibility" of variables in the case of multi-threaded concurrency. What is visibility? That is, if the value of the variable is modified in the working memory of a thread, the value of the variable can be echoed to the main memory immediately, thereby ensuring that all threads see the same value of the variable. So it plays a significant role in dealing with synchronization issues, and its overhead is smaller than synchronized, and its use cost is lower. 
For example: in writing singleton mode ( the article dedicated to singleton is here ), in addition to using static inner classes, there is also a very popular way of writing, which is Volatile+DCL:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

In this way, no matter in which thread the singleton is created, all threads share the singleton.

Although this Volatile keyword can solve the synchronization problem in a multi-threaded environment, it is also relative because it does not have the atomicity of operations, that is

It is not suitable for writing operations on the variable to depend on the variable itself (this is very important, understand carefully) .

These two chestnuts are amazing, haha!

For the simplest example: count++ is actually count=count+1; when performing a counting operation, and the final value of count depends on its own value. Therefore, when using volatile modified variables to perform such a series of operations, there is a problem of concurrency 

For example: because it does not have the atomicity of the operation, it is possible that the count value of thread 1 is 4 when it is about to write; and thread 2 just gets the value 4 before the write operation, so thread 1 is finishing After its write operation, the count value is 5, and the count value in thread 2 is still 4. Even if thread 2 has completed the write operation, the count is still 5, and we expect that the count will eventually be 6, so This has concurrency issues. And if count is changed to this: count=num+1; assuming that num is synchronized, then there is no concurrency problem with count, as long as the final value does not depend on itself .

usage

Because volatile does not have the atomicity of operations, if a variable modified with volatile performs operations that depend on itself, there will be concurrency problems, such as: count, writing the following in a concurrent environment will not achieve any effect :

public class Counter {
    private volatile int count;

    public int getCount(){
        return count;
    }
    public void increment(){
        count++;
    }
}

If count can maintain data consistency in a concurrent environment, you can add synchronized synchronization lock modification to increment(), and the improved one is:

public class Counter {
    private volatile int count;

    public int getCount(){
        return count;
    }
    public synchronized void increment(){
        count++;
    }
}

Internal implementation

The assembly instruction implementation 
can be found in this article: Volatile Implementation Principle

3、synchronized

effect

Synchronized is called synchronization lock, which is a simplified version of Lock. Because it is a simplified version, its performance is definitely not as good as Lock, but it is convenient to operate. It only needs to wrap the code block that needs to be synchronized in a method or inside it. The code segment is synchronized. All threads accessing the code in this area must first hold the lock before entering, otherwise the interception will wait outside and wait for the thread holding the lock to finish processing before acquiring the lock to enter, precisely because it is based on this blocking. strategy, so its performance is not very good, but due to the advantages of operation, it only needs to be simply declared, and the code block declared by it also has the atomicity of operation.

usage

    public synchronized void increment(){
            count++;
    }

    public void increment(){
        synchronized (Counte.class){
            count++;
        }
    }

Internal implementation

Reentrant Lock + a Condition, so it is a simplified version of Lock, because a Lock can often correspond to multiple Conditions

4、ThreadLocal

effect

Regarding ThreadLocal, the appearance of this class is not used to solve the problem of resource sharing in a multi-threaded concurrent environment. It is different from the other three keywords. The other three keywords are all from outside the thread to ensure the consistency of variables. In this way, the variables accessed by multiple threads are consistent, which can better reflect the sharing of resources.

The design of ThreadLocal is not to solve the problem of resource sharing , but to provide local variables within the thread, so that each thread manages its own local variables, and the data operated by other threads will not affect me. It does not affect, so there is no solution to resource sharing. If it is to solve resource sharing, then I need to obtain the results of other thread operations, and ThreadLocal manages itself, which is equivalent to encapsulating inside Thread for threads. Manage yourself.

usage

Generally, ThreadLocal is used. It is officially recommended that we define it as private static (this is very important, and it will be clear from the following figure) . As for why it should be defined as static, it is related to memory leakage, which will be discussed later. It has three exposed methods, set, get, and remove.

public class ThreadLocalDemo {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "hello";
        }
    };
    static class MyRunnable implements Runnable{
        private int num;
        public MyRunnable(int num){
            this.num = num;
        }
        @Override
        public void run() {
            threadLocal.set(String.valueOf(num));
            System.out.println("threadLocalValue:"+threadLocal.get());
        }
    }

    public static void main(String[] args){
        new Thread(new MyRunnable(1));
        new Thread(new MyRunnable(2));
        new Thread(new MyRunnable(3));
    }
}

The running results are as follows. These ThreadLocal variables are managed internally by the thread and do not affect each other:

threadLocalValue:2 
threadLocalValue:3 
threadLocalValue:4

For the get method, if ThreadLocal has no set value, it returns null by default, so if there is an initial value, we can override the initialValue() method, and call get when there is no set value and return the initial value.

A point worth noting : After ThreadLocal is finished using the thread, we should manually call the remove method to remove its internal value, which can prevent memory leaks, and of course set it to static.

Internal implementation

There is a static class ThreadLocalMap inside ThreadLocal. Threads using ThreadLocal will be bound to ThreadLocalMap and maintain the Map object. The function of this ThreadLocalMap is to map the value corresponding to the current ThreadLocal, and its key is the weak reference of the current ThreadLocal: WeakReference

memory leak problem

For ThreadLocal, it has always involved the problem of memory leakage, that is, when the thread does not need to operate the value in a ThreadLocal, it should be manually removed. Why? Let's take a look at the connection diagram between ThreadLocal and Thread: 
This diagram comes from the network: 
write picture description here

The dotted line represents a weak reference. As can be seen from the figure, a Thread maintains a ThreadLocalMap object , and the key of the Map object is provided by the weak reference of the ThreadLocal object that provides the value, so this is the case: 
if ThreadLocal does not If it is set to static, because the life cycle of Thread is unpredictable, it will be recycled when the system gc, and the ThreadLocal object is recycled. At this time, its corresponding key must be null, which leads to the corresponding key of the key. The value cannot be taken out, and the value was previously referenced by Thread, so there is a key that is null and a strong reference to the value, which prevents the Entry from being recycled, resulting in memory leaks.

Therefore, the way to avoid memory leaks is to set ThreadLocal to be static . In addition to this, you must manually remove the value of the ThreadLocal when the thread does not use its value, so that the Entry can be recycled normally when the system is gc. Regarding the recycling of ThreadLocalMap, it will be recycled after the current Thread is destroyed.

Summarize

Regarding the Volatile keyword having visibility, but not atomicity of operations,

Synchronized consumes more resources than volatile, but it can ensure the atomicity of variable operations and the consistency of variables. The best practice is to use the two together.

1. The emergence of synchronized is to solve the problem of multi-threaded resource sharing. The synchronization mechanism adopts the method of "time for space": access serialization and object sharing. The synchronization mechanism is to provide a variable that all threads can access.

2. For the emergence of Atomic, it is completed through atomic operation instruction + Lock-Free, thus realizing non-blocking concurrency problem.

3. For Volatile, some requirements are solved for the multi-threaded resource sharing problem. In the case of independent operation, changes to variables will be visible to any thread.

4. For the emergence of ThreadLocal, it is not to solve the problem of multi-threaded resource sharing, but to provide local variables within the thread, eliminating the unnecessary trouble of parameter passing. ThreadLocal adopts the method of "exchanging space for time": Access parallelism and object exclusiveness. ThreadLocal provides a unique variable for each thread, and each thread does not affect each other.

Guess you like

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