本地线程-ThreadLocal
线程本地存储是一个自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。简单来说,就是对于某个变量,针对不同的线程存储不同的值。
实例:
import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * @Description * @Author KToTo * @Date 2019/3/18 22:22 **/ public class ThreadLoaclVariableHolder { //创建一个全局的ThreadLocal对象 private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){ private Random random = new Random(47); //初始化方法,此处的Random相当于共享变量,为了使演示效果明显, //故将该初始化方法同步 protected synchronized Integer initialValue() { return random.nextInt(1000); } }; //提供公有的递增和获取方法 public static void increment() { value.set(value.get() + 1); } public static int get() { return value.get(); } public static void main(String[] args) throws InterruptedException { ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { pool.execute(new Accessor(i)); } //与Thread.sleep(long mils)方法功能一致 TimeUnit.SECONDS.sleep(3); //立即关闭线程池,不熟悉的读者可以自行百度shutdown和shutdownNow的区别 pool.shutdownNow(); } } class Accessor implements Runnable { private final int id; public Accessor(int id) { this.id = id; } @Override public void run() { //响应中断 while (!Thread.currentThread().isInterrupted()) { ThreadLoaclVariableHolder.increment(); System.out.println(this); //对于多核处理器,此方法可以省略 Thread.yield(); } } @Override public String toString() { return "Accessor{" + "id=" + id + ":" + ThreadLoaclVariableHolder.get() + '}'; } }
原理分析
- 从概念上来看,你可以将ThreadLocal<T>视为包含了Map<Thread, T>对象,其中保存了特定于该线程的值,但是实际上并非如此,这些特定于线程的值其实是保存在Thread对象中的,当线程终止后,这些值会作为垃圾回收。
- 首先简单概括一下,当我们使用ThreadLocal的时候,我们想要的效果是针对该共享变量,每个线程中访问该变量的值是不一样的,而且是互不影响的。而ThreadLocal可以达到这一效果,其实是在每一个Thread对象中都有一个ThreadLocalMap的实例对象,当当前线程对改ThreadLocal进行初始化的时候,会以ThreadLocal的对象为key,值为value,存放到当前线程对象中的ThreadLocalMap。
- 在上述的示例中,我们可以看到我们直接使用了ThreadLocal的Get方法,而不是先Set然后Get,下面让我们从Get方法入手,来了解一下ThreadLocal的实现
public T get() { Thread t = Thread.currentThread(); //获取当前线程Thread对象中存放的ThreadLocalMap。 ThreadLocalMap map = getMap(t); //判断当前对象是否存在本地线程对象 if (map != null) { //判断当前线程对象中的threadLocals是否存储当前对象(注意这个this)的值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果上述两个判断均不满足,则进行初始化 return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //关于ThreadLocalMap类的定义请参考源码 public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }
/** *调用重写的initialValue()方法获取初始值, *然后将当前ThreadLocal的初始化值添加到Thread对象的threadLocals变量中。 **/ 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; } 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(); }
ThreadLocal的副作用
- 脏数据,线程复用会导致产生脏数据。由于线程池会重用Thread对象,那么与Thread绑定的静态属性ThreadLocal变量也会被复用
- 内存泄露,当ThreadLocal为静态属性时,就不可以寄希望于ThreadLocal对象失去引用时,触发弱引用机制来回收就不现实了。
- 针对上述两种问题的解决办法就是在每次使用完ThreadLocal之后,必须要及时的调用remove()方法清理。
参考:《Thinking In Java》、《Java并发编程实战》、《码出高效》