ThreadLocal的原理与使用

时间:2019-11-14
本文章向大家介绍ThreadLocal的原理与使用,主要包括ThreadLocal的原理与使用使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

  在java web项目中,经常会使用到单例对象,从服务器启动那一时刻就实例化全局对象。然后会对某些全局对象的属性进行修改之类的操作,但是我们知道项目一般都是部署到tomcat、Jboss之类的服务器上。浏览器的每个请求就是一个新的线程,这样如果  对全局对象的属性进行修改并使用,很可能就会造成数据不一致的错误问题。那怎么保证各自线程能正确使用自己修改过的共享变量呢?这时让我们想到ThreadLocal,那ThreadLocal是什么,为何能有如此神奇的行为呢?带着这个问题我们直接进入主题。

什么是ThreadLocal?

  ThreadLocal是java.lang包下面的一个类。见名知意,局部的线程。它能避免发生多线程对共享变量修改造成的数据错误问题!

ThreadLocal的底层原理

  ThreadLocal能使变量值和线程对象关联起来,保证线程封闭。下面是ThreadLocal类主要的方法:

1 public T get();
2 public void set(T value);
3 public void remove();
4 private T setInitialValue();

  get方法是获取保存在ThreadLocal中当前线程设置的共享变量副本值;

  set方法是用来设置当前线程的共享变量副本;

  set方法

1 public void set(T value) {
2         Thread t = Thread.currentThread();
3         ThreadLocalMap map = getMap(t);
4         if (map != null)
5             map.set(this, value);
6         else
7             createMap(t, value);
8     }

  set方法会获取当前线程(线程中有一个属性threadLocals,该属性属于ThreadLocal.ThreadLocalMap类)。如果当前线程的ThreadLocalMap对象不为空,直接把修改的值存放到ThreadLocalMap中;如果为空,则先实例化ThreadLocalMap对象,再存值。

ThreadLocalMap和HashMap相似,也是通过哈希表的数据结构来保存数据(数组加链表)。从set方法可以知道,一个线程有且只会创建一个ThreadLocalMap对象,线程会把修改后的变量值(变量副本)保存到ThreadLocalMap中。

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

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

  get方法

 1 public T get() {
 2         Thread t = Thread.currentThread();
 3         ThreadLocalMap map = getMap(t);
 4         if (map != null) {
 5             ThreadLocalMap.Entry e = map.getEntry(this);
 6             if (e != null)
 7                 return (T)e.value;
 8         }
 9         return setInitialValue();
10 }

  get方法会获取到当前线程ThreadLocalMap对象中保存的副本值。

注意:同一个ThreadLocal对象下,保存在ThreadLocalMap中的Entry对象下标相同。因为下标计算=threadLocalHashCode & (len-1); threadLocalHashCode 和len都是相同的。

  remove方法

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

  获取当前线程的ThreadLocalMap对象,对象不为空,则调用remove(this);看看这个方法做了什么。

private void remove(ThreadLocal key) {
            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)]) {
                if (e.get() == key) {
                    e.clear(); 
                    expungeStaleEntry(i); // 清空Entry[] 数组对应下标的对象
                    return;
                }
            }
        }

  remove的目的是帮助GC清除多余对象,避免造成内存溢出。

实际项目中的运用

原文地址:https://www.cnblogs.com/prc0109/p/11855805.html