Java Concurrency ThreadLocal

Introduction to ThreadLocal

ThreadLocal translates to thread local variables. Beginners may think that ThreadLocal refers to a Thread. In fact, to put it bluntly, ThreadLocal is a member variable, but it is a special variable - the variable value is always the same as the current thread (calling Thread.currentThread () get) associated. Since ThreadLocal is a variable, what is its role? The abstract point is to provide thread closure, and the concrete point is to provide a copy of the variable for each thread that uses the variable, so that each thread using the variable has a copy, so that the thread between the threads will communicate with each other to the variable. The access to the variable is isolated, and the operation of the variable does not affect each other.

When accessing shared mutable data (because there are immutable data of final type), synchronization mechanisms are usually used, because synchronization requires locking, so efficiency may be affected. One way to avoid using synchronization is to not share data. Because accessing data within a single thread does not require synchronization. This is the explanation for thread closure, and it is also the core idea of ​​ThreadLocal design. When an object is enclosed within a thread by a thread, the object automatically achieves thread safety. What exactly does ThreadLocal do? It associates a value in the thread with the current thread, implementing "set once and call everywhere".

Therefore, comparing the synchronization mechanism with ThreadLocal, it can be concluded that synchronization realizes thread data sharing by locking, that is, time for space, while ThreadLocal realizes thread data sharing by changing space for time in the form of variable copies.

Design a ThreadLocal

According to the above description, the key to designing ThreadLocal is to associate a value with the object that accesses the value, that is, the current thread. The following code implements this functionality:

package com.rhwayfun.patchwork.concurrency.r0408;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by rhwayfun on 16-4-8.
 */
public class DemoThreadLocal {

    /**
     * 用来关联值与当前线程的Map
     */
    private Map<Thread,Object> localMap = Collections.synchronizedMap(new HashMap<Thread, Object>());

    /**
     * 设置值与线程关联
     * @param copyValue
     */
    public void set(Object copyValue){
        //1、key为当前访问值的线程,value为值的副本
        localMap.put(Thread.currentThread(),copyValue);
    }

    /**
     * 得到当前线程关联的值
     * @return
     */
    public Object get(){
        //获取当前线程
        Thread currentThread = Thread.currentThread();
        //根据当前线程得到值
        Object value = localMap.get(currentThread);
        if (value == null || !localMap.containsKey(currentThread)){
            value = initialValue();
            localMap.put(currentThread,value);
        }
        return value;
    }

    /**
     * 对值进行初始化
     * @return
     */
    protected Object initialValue() {
        return null;
    }
}

This is probably the simplest version of ThreadLocal. When using DemoThreadLocal as an internal private immutable class, you can achieve the simple function of "setting one setting and calling everywhere". However, in engineering practice, the design needs to consider many more issues, and the design is more complicated.

The design principle of ThreadLocal

ThreadLocal is often used to prevent sharing of mutable single-instance variables or global variables . In a single thread it is often possible to use a global database connection to avoid the need to instantiate the database connection each time each method is called. The database connection usually used in JDBC uses ThreadLocal, and each thread has its own database connection, which achieves the purpose of thread isolation. The code usually looks like this:

package com.rhwayfun.patchwork.concurrency.r0408;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * Created by rhwayfun on 16-4-8.
 */
public class ConnectionManager {

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
        @Override
        protected Connection initialValue() {
            Connection conn = null;
            try {
                conn = DriverManager.getConnection(
                        "jdbc:mysql://localhost:3306/test", "username",
                        "password");
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return conn;
        }
    };

    public static Connection getConnection() {
        return connectionHolder.get();
    }

    public static void setConnection(Connection conn) {
        connectionHolder.set(conn);
    }
}

The above code also demonstrates how to use ThreadLocal. Let's analyze how ThreadLocal associates the current thread with the accessed value? In fact, the principle is the same as the implementation of the simplified version, both through a map, but in the implementation of ThreadLocal , it is ThreadLocalMap, which is a variable of ThreadLocal, you can see it by looking at the code:

    public void set(T value) {
        //得到当前线程
        Thread t = Thread.currentThread();
        //根据当前线程得到一个map
        ThreadLocalMap map = getMap(t);
        //如果map不为空则调用set进行关联
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

The above code is exactly the same as the simplified version. First, the ThreadLocalMap object is obtained according to the current thread. If the map is not empty, the current thread is directly associated with the value (accessed value); if the map is empty, a ThreadLocalMap is created.

Through the source code, it can be found that ThreadLocalMap is a static inner class of the ThreadLocal class. It implements the setting and acquisition of key-value pairs (compare to the Map object), and each thread has an independent copy of ThreadLocalMap. The value it stores is only Can be read and modified by the current thread. The ThreadLocal class implements the isolation of variable access in different threads by manipulating each thread-specific ThreadLocalMap copy. Because each thread's variables are unique to themselves, there will be no concurrency errors at all. Another point is that the key in the key-value pair stored in ThreadLocalMap is the this object, which refers to the ThreadLocal object, and the value is the object you set (here, Connection).

    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

As you can see from the code, getMap is to get a variable named threadLocals, and the type of this variable is ThreadLocalMap, which means that there is a ThreadLocalMap for each different thread. In this way, each thread has a ThreadLocalMap, which can achieve isolation between threads. So thread operations on variables actually save a copy of the value in their respective ThreadLocalMap. Let's see how the ThreadLocalMap is set up:

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

If you are familiar with HashMap, this is actually a put operation of HashMap: first, interpret whether there is an Entry whose key is the incoming key in the Entry array. If it exists, it will be overwritten; if the key is null, it will be replaced. If none of the above conditions are met, create an Entry object and put it in the Entry array.

Next, see how the get method is implemented:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

When obtaining the value bound to the current thread, the ThreadLocalMap object is searched with the ThreadLocal object pointed to by this as the key, which corresponds to the code of the previous set() method. If it is found through this as the key before, it will return directly, and if it is not found, the setInitialValue() method will be called. This method first gets the value initialized in the implementation code (Connection in our code, that is, the value is Connection), and then performs the same operation as the previous set method.

Since each thread has its own ThreadLocalMap when ThreadLocal is used, will there be an OOM problem? The answer can be found in the following source code:

static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

It can be seen that the Entry object is a weak reference. According to the characteristics of weak references: in the process of scanning the memory area under its jurisdiction, the garbage collector thread finds an object with only weak references, regardless of whether the current memory space is sufficient or not. will reclaim its memory. Therefore, after the thread is terminated, the ThreadLocalMap object will be collected as garbage, and naturally there is no need to worry about memory leaks.

A complete ThreadLocal example

package com.rhwayfun.patchwork.concurrency.r0408;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * Created by rhwayfun on 16-4-8.
 */
public class PersonThreadLocalDemo {

    private static final ThreadLocal<Person> personLocal = new ThreadLocal<>();
    private static final Random ran = new Random();
    private static final DateFormat format = new SimpleDateFormat("HH:mm:ss");

    /**
     * 不同的线程并发修改Person的age属性
     */
    static class Wokrer implements Runnable{
        @Override
        public void run() {
            doExec();
        }

        private void doExec() {
            System.out.println(Thread.currentThread().getName() + " start task at "
                    + format.format(new Date()));
            //不同的线程会会将age属性设置成不同的值
            int age = ran.nextInt(20);
            Person p = getPerson();
            //设置年龄
            p.setAge(age);
            System.out.println(Thread.currentThread().getName() + ": set age to " + p.getAge() + " at "
                + format.format(new Date()));
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": get age " + p.getAge() + " at "
                + format.format(new Date()));
        }

        protected Person getPerson() {
            Person p = personLocal.get();
            if (p == null){
                p = new Person();
                personLocal.set(p);
            }
            return p;
        }
    }

    public static void main(String[] args){
        Wokrer wokrer = new Wokrer();
        new Thread(wokrer,"worker-1").start();
        new Thread(wokrer,"worker-2").start();
    }
}

The results are as follows:

operation result

ThreadLocal Summary

  1. ThreadLocal refers to thread local variables, not Thread
  2. ThreadLocal is mainly used to solve the problem of inconsistency of data in multiple threads due to concurrency. That is to say, if you want each thread to operate on shared data without affecting each other, but you don't want to use synchronization to solve it, then ThreadLocal will be your dish.
  3. The core of ThreadLocal to achieve thread isolation is to create a ThreadLocalMap for each thread that accesses the value, so that different threads can operate on shared data without affecting each other.
  4. The difference from synchronized: synchronized is used for data sharing between threads, while ThreadLocal is used for data isolation between threads. The fields used by the two are different. ThreadLocal does not appear to replace synchronized, and ThreadLocal cannot achieve atomicity, because the actual scope of operation of ThreadLocal's ThreadLocalMap is single-threaded and has nothing to do with multi-threading.
  5. Will the ThreadLocalMap created by using ThreadLocal in a multi-threaded situation run out of memory: the answer is no. Because the Entry that stores the data is a weak reference, it will be automatically garbage collected after the thread execution ends.

Guess you like

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