深入理解ThreadLocal

58 阅读3分钟

1 ThreadLocal

1.1 为什么要有ThreadLocal

每次执行数据库方法时需要获取一个连接,如果每次都去重新获取连接,那么想要实现事务比较麻烦,这个时候就可以使用ThreadLocal

private static int insert(String stu) {
  Connection coon = getConn();
  int i  = 0;
  String sql = "insert into stu(name, sex, age) values(?,?,?)"
  PreparedStatement pstmt;
  try{
    pstmt = (PreparedStatement) conn.prepareStatement(sql);
    pstmt.setString(1, stu);
    i = pstmt.executeUpdate();
    pstmt.close();
  }catch(SQLException e){
    e.printStackTrace();
  }
  return 1;
}


ThreadLocal类提供线程局部变量。可以用于在一个线程中进行跨方法的变量传递

1.2 ThreadLocal的使用


public class UseThreadLocal {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();

    public void startThArray(){
        Thread[] runs = new Thread[3];

        for (int i = 0; i < runs.length ; i++) {
            new TestThread(i).start();
        }
    }

    public static class TestThread extends Thread{
        int id;

        public TestThread(int id){
            this.id = id;
        }

        public void run(){
            String thName = Thread.currentThread().getName();

            threadLocal.set("thread_" + id);
            if (id == 2){
                threadLocal2.set(id);
            }
            System.out.println(thName + ":" + threadLocal.get());
            System.out.println(thName + ":" + threadLocal2.get());

        }
    }

    public static void main(String[] args) {
        UseThreadLocal useThreadLocal = new UseThreadLocal();
        useThreadLocal.startThArray();
    }


}

ThreadLocal的实现方式

ThreadLocal是在Thread里边使用ThreadLocalMap作为成员变量去实现的

image.png

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

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}
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;
    }
  }
  
  private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
  }
  
  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();
  }
  
}

1.2.1 Hash冲突解决

开放定址法

基本思想是,出现冲突后按照一定算法查找一个空位置存放,根据算法的不同又可以分为线性探测再散列、二次探测再散列、伪随机探测再散列。

线性探测再散列即依次向后查找;二次探测再散列, 即依次向前后查找增量为 1、2、3的二次方;伪随机,顾名思义就是随机产生一个增量位移。

ThreadLocal解决hash冲突使用的开放定址法中的线性探测再散列

链地址法

这种方法的基本思想是将所有哈希地址为 i 的元素构成一个称为同义词链的 单链表, 并将单链表的头指针存在哈希表的第 i 个单元中, 因而查找、插入和删除主要在同一链表中进行。 链地址法适用于经常进行插入和删除的情况。 Java里的 HashMap 用的就是链地址法,为了避免 hash 洪水攻击,1.8 版本开始还引入了红黑树。

再哈希法

这种方法是同时构造多个不同的哈希函数: Hi=RH1(key) i=1 ,2 ,… ,k ,当哈希地址 Hi=RH1(key)发生冲突时,再计算 Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

公共溢出表

这种方法的基本思想是: 将哈希表分为基本表和溢出表两部分, 凡是和基本 表发生冲突的元素, 一律填入溢出表。

1.3 ThreadLocal内存泄漏分析

如果在thread的run方法中设置了ThreadLocal后,这个线程只要存在,那么他Entry下的value指向的对象不会被回收,就会导致内存泄漏。所以在使用ThreadLocal时,如果不使用了,就需要手动remove掉

1722178655811.jpg

1.4 ThreadLocal线程安全问题

如果ThreadLocal设置对象,当他们对象的引用指向同一个对象实例,就会产生一个线程对该数据的修改修改了其他线程的ThreadLocal的修改