天天看点

Java多线程--ThreadLocal--原理

简介

说明

        本文介绍Java的ThreadLocal的原理。

        ThreadLocal是Java多线程常用的技术,也是Java后端面试中常见的问题。

ThreadLocal简介

        ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

ThreadLocal用法

要明白原理,先要知道用法。​

本文将最简用法贴出:

package com.example.a;

public class Demo {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    static void print(String str){
        System.out.println(str + ":" + threadLocal.get());
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("abc");
                print("thread1 variable");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("def");
                print("thread2 variable");
            }
        });

        thread1.start();
        thread2.start();
    }
}      

运行结果

thread1 variable:abc
thread2 variable:def      

流程追踪

追踪上边简单用法的流程

set流程

threadLocal.set("abc");         // ThreadLocal#set
  //传进来的参数名为value

  //获取当前线程
  Thread t = Thread.currentThread();

  //获得此线程的ThreadLocalMap变量。(若为null则创建它,若有则直接使用)
  //    ThreadLocalMap是ThreadLocal的静态内部类。即:ThreadLocal.ThreadLocalMap
  //    ThreadLocalMap内部是弱引用(WeakReference)
  ThreadLocalMap map = getMap(t);   // ThreadLocal#getMap
    return t.threadLocals;

  //保存数据。key为当前ThreadLocal对象,value为传进来的值
  map.set(this, value)      

get流程 

threadLocal.get()               // ThreadLocal#get
    //获取当前线程
    Thread t = Thread.currentThread();

    //获得此线程的ThreadLocalMap变量
    ThreadLocalMap map = getMap(t);

    //获得当前线程的以当前ThreadLocal为key的值
    ThreadLocalMap.Entry e = map.getEntry(this);

    //强制转换为原来的类型并返回
    T result = (T)e.value;
    return result;      

源码追踪 

set源码

追踪java.lang.ThreadLocal#getMap

Java多线程--ThreadLocal--原理

它返回的是t的threadLocals变量,追踪它

Java多线程--ThreadLocal--原理

可以看到,每个线程(Thread)内部都持有一个ThreadLocal.ThreadLocalMap实例

追踪map.set(this, value)

也就是:java.lang.ThreadLocal.ThreadLocalMap#set

private void set(ThreadLocal<?> key, Object value) {

  // We don't use a fast path as with get() because it is at
  // least as common to use set() to create new entries as
  // it is to replace existing ones, in which case, a fast
  // path would fail more often than not.

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

Entry[] tab = table;

tab[i] = new Entry(key, value);

        注意上边这两行代码,可以看到,数据最终是存到了Entry[] table这个数组里边。

        追踪Entry,它的源码如下:

public class ThreadLocal<T> {
    // 其他代码
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

Entry继承自WeakReference,这个与内存泄露有关。后边会专门写一篇这个博客。

key为ThreadLocal对象,value为传进来的对象。

get源码

追踪ThreadLocalMap.Entry e = map.getEntry(this)

java.lang.ThreadLocal.ThreadLocalMap#getEntry

private Entry getEntry(ThreadLocal<?> key) {
  int i = key.threadLocalHashCode & (table.length - 1);
  Entry e = table[i];
  if (e != null && e.get() == key)
    return e;
  else
    return getEntryAfterMiss(key, i, e);
}      

可以看到,它以ThreadLocal对象为key去java.lang.ThreadLocal.ThreadLocalMap#table去查找value。

原理总结(图文)

Java多线程--ThreadLocal--原理