ThreadLocal理解

ThreadLocal

怎么理解呢,在我看来就是给每一个Thread提供私有的空间,用来存放数据,这个数据可以再在Thread整个存活周期内被拿到

ThreadLocal 的两种典型的使用场景

1、任务多且每一个都需要,存在并发安全,比如SimpleDateFormat ,每一个任务都需要,但是又不是并发安全的类,如果在每一个任务里面都new一个SimpleDateFormat 对象的话,太占用内存了,如果只new一个SimpleDateFormat 对象的话有需要加锁,影响并发性能,但是这个类只是拿来做格式化,因为这个影响性能很明显划不着,这个时候就直接使用ThreadLocal ,给每一个线程创建一个SimpleDateFormat 对象,在线程池中的线程是可以服用的,所以并不会创建很多线程

2、每一个线程需要单独保存信息,在一个Thread里面执行了多个方法或者方法里面在调用其他方法,可以利用ThreadLocal 来传递参数,比如说用户信息,但是每一个线程的用户信息肯定是不一定一样的

public class LocalData {
   public String id;

   @Override
   public String toString() {
      return "LocalData{" +
            "id='" + id + '\'' +
            '}';
   }

   public String getId() {
      return id;
   }

   public void setId(String id) {
      this.id = id;
   }
}
public class LongLocal {
   public static ThreadLocal<LocalData> longThreadLocal=new ThreadLocal<LocalData>(){
      @Override
      protected LocalData initialValue() {
         LocalData localData = new LocalData();
         System.out.println("初始化的hashcode:"+localData.hashCode());
         return localData;
      }
   };
}
public class LocalThreadTest {
   public static void main(String[] args) {
      ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 5, TimeUnit.SECONDS, new LinkedBlockingQueue());
      for (int i = 0; i < 100; i++) {
         threadPoolExecutor.submit(()->{
            try {
               TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"--"+LongLocal.longThreadLocal.get().hashCode());
         });
      }
      threadPoolExecutor.shutdown();
   }
}
初始化的hashcode:541662399
初始化的hashcode:699997381
初始化的hashcode:2098748799
pool-1-thread-10--2098748799
初始化的hashcode:1915851000
初始化的hashcode:2057263935
初始化的hashcode:993438493
初始化的hashcode:1392591561
初始化的hashcode:1532882474
初始化的hashcode:1182696684
初始化的hashcode:136556034
pool-1-thread-6--1182696684
pool-1-thread-8--1532882474
pool-1-thread-5--1392591561
pool-1-thread-1--993438493
pool-1-thread-9--2057263935
pool-1-thread-2--1915851000
pool-1-thread-3--699997381
pool-1-thread-7--541662399
pool-1-thread-4--136556034
pool-1-thread-1--993438493
pool-1-thread-8--1532882474
pool-1-thread-10--2098748799
pool-1-thread-5--1392591561
pool-1-thread-4--136556034
pool-1-thread-7--541662399
pool-1-thread-6--1182696684

可以发现每一个线程拿到的hashcode都是不同的,并且重复使用线程执行任务得到的hashcode是一样的,

对于共享的对象,如果使用ThreadLocal 可以理解为用空间换时间,就是每一个线程拷贝一个副本到自己线程,然后自己玩自己的,互不干扰,避免了资源的竞争

但是ThreadLocal 并不能解决共享资源的并发问题,因为对于 ThreadLocal 而言,每个线程中的资源并不共享

Thread、 ThreadLocal 及 ThreadLocalMap 的关系
在这里插入图片描述

每一个Thread都有一个自己的ThreadLocalMap ,一个ThreadLocalMap 里面可能放了很多个ThreadLocal

//ThreadLocal 的set方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

通过map.set(this, value);可以看出在存储数据到ThreadLocalMap 里面的时候key是当前ThreadLocal 自身,值是要保存的对象

static class ThreadLocalMap {

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

    private Entry[] table;
}

ThreadLocalMap会有一个 Entry 类型的数组,名字叫 table。Entry 是键值对类型

ThreadLocalMap在存数据时如果发生hash冲突的时候,使用的是线性探测法,就是会寻找下一个空的位置,而不是和hashmap一样使用链表往下接

ThreadLocal的内存泄漏问题

Entry extends WeakReference<ThreadLocal<?>>发现,key也就是ThreadLocal其实使用的是弱引用,在GC的时候是可以被回收的,也就是key不会发现内存泄漏问题

value = v;发现,value也就是保存的数据使用的是强引用,在GC的时候是不会被回收的,也就是value是会发现内存泄漏问题(如果线程一直在执行耗时的任务不停止,jvm执行可达性分析的时候是会存在value的强引用的)

如果想避免value的内存泄漏,可以将value置为null

//ThreadLocalMap的remove方法
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);
            return;
        }
    }
}
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

从源码中可以发现,remove方法里面有e.value = null;是将value置为了null,也就可以解决内存泄漏的问题,如果后面我们不在使用ThreadLocal中的数据的话,可以调用remove将value置为null,解决内存泄漏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值