ThreadLocal解决多线程问题

ThreadLocal简介

多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证线程安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的都是线程自己的变量这样就不会存在线程不安全得问题。
在JDK 1.2的版本中就提供了java.lang.ThreadLocal。ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。
在JDK 1.5中,ThreadLocal已经广泛支持泛型,该类的类名已经变为ThreadLocal,API方法也相应调整为void set(T value)、T get()以及T initialValue()。

ThreadLocal简单使用

利用ThreadLocal记录用户信息

import com.lin.missyou.model.User;

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

public class LocalUser {
    private static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();

    public static void set(User user, Integer scope) {
        Map<String, Object> map = new HashMap<>();
        map.put("user", user);
        map.put("scope", scope);
        LocalUser.threadLocal.set(map);
    }

    public static void clear() {
        LocalUser.threadLocal.remove();
    }

    public static User getUser() {
        Map<String, Object> map = LocalUser.threadLocal.get();
        User user = (User)map.get("user");
        return user;
    }

ThreadLocal实现原理

set()方法源码

ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。

	public void set(T value) {
	//1.获取当前调用者线程
        Thread t = Thread.currentThread();
   //2.以当前线程作为key值,去查找对应的线程变量,找到对应的map     
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

	    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

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

从源码可以看出每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,若使用时已经存在threadLocals则直接使用已经存在的对象。

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
//构造方法
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
}

通过上面的源码可以看出在实例化ThreadLocalMap时创建了一个长度为16的Entry数组table。通过hashCode与length位运算确定出一个索引值i,这个i就是被存储在table数组中的位置。每个线程Thread持有一个ThreadLocalMap类型的实例threadLocals,也就是说每个线程Thread都持有一个Entry型的数组table,一切读取过程都是通过操作这个数组table完成的。

get()方法源码

    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;
    }

理解了set()方法,get()方法就很好理解了,就是通过当前线程计算出索引值,然后从数组对应位置读取出来即可。

remove()方法源码

remove方法就是删除当前线程中指定的threadLocals变量

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

总结

ThreadLocal中set和get操作的都是对应线程的table数组,因此在不同的线程中访问同一个ThreadLocal对象的get()和set()进行存取数据不会相互干扰。

ThreadLocal与Synchronized

ThreadLocalSynchronized都是为了解决多线程中相同变量的访问冲突问题,不同点是

  • Synchronized是通过线程等待,牺牲时间 来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

参考文章:
Java中的ThreadLocal详解
ThreadLocal
ThreadLocal作用、场景、原理

猜你喜欢

转载自blog.csdn.net/xfx_1994/article/details/105289539