ThreadLocal大家应该都不陌生,见过最多的使用场景应该是和SimpleDateFormat一起使用吧,因为这个SDF非线程安全的,所以需要使用ThreadLocal将它在线程之间隔离开,避免造成脏数据的🐞。那么ThreadLocal是怎么保证线程安全,又是如何操作的呢?
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static void main (String[] args) { ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); new Thread(new Runnable() { @Override public void run () { threadLocal.set(1 ); threadLocal.set(2 ); System.out.println("cc1: " + threadLocal.get()); } }, "cc1" ).start(); new Thread(new Runnable() { @Override public void run () { System.out.println("cc2: " + threadLocal.get()); } }, "cc2" ).start(); }
输出:
哦哟~cc2打印出来null,也就是在cc1线程中设置的值在线程cc2中获取不到,这也就是所谓的线程隔离,我们来看下ThreadLocal具体的代码实现吧:
ThreadLocal的set(T t)方法源码
1 2 3 4 5 6 7 8 9 10 11 12 public void set (T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) map.set(this , value); else createMap(t, value); }
ThreadLocalMap的set(ThreadLocal key, Object value)方法源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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(); }
ThreadLocal的createMap(Thread t, T firstValue)方法源码
1 2 3 4 5 void createMap (Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this , firstValue); }
ThreadLocalMap的构造器源码
1 2 3 4 5 6 7 8 9 10 11 12 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); }
以上便是ThreadLocal达到线程隔离的基本解析,讲解的比较基础,其实就是JDK源码鉴赏,还有什么不懂的地方就自己去看源码吧。
延伸下
ThreadLocal的get()方法源码
1 2 3 4 5 6 7 8 9 10 11 12 13 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(); }
这段代码比较简单,这里就不在进行解释了,我们着重看一下最后一句setInitialValue()
这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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; } protected T initialValue () { return null ; }
会发现和set方法类似,只不过是将一个null
当做value而已,所以我们在没给ThreadLocal设置值的情况下调用get方法,则会为其创建一个默认的null值并返回null。
留一个思考题
因为我们每个线程的ThreadLocal的key的hash值都是固定的,那么Thread的threadLocals变量的table中会有多少个非null元素呢?