ThreadLocal详解

来源:互联网 发布:java多线程抢票代码 编辑:程序博客网 时间:2024/06/05 07:39

概述


ThreadLocal用来解决多线程环境中资源竞争的问题。它不是通过加锁的方式,而是通过每个线程保存一份资源的副本,这样各个线程访问本线程自己的那一份儿,从而避免了对资源竞争的问题


用法

ThreadLocal的用法非常的简单,只用操作两个方法即可:

  • set(Tvalue)  往本线程中保存值
  • T get() 从本线程中获取值

下面给出一个实例:
public class ThreadLocalExample {    public static class MyRunnable implements Runnable {        private ThreadLocal<Integer> threadLocal = new ThreadLocal<>();        public void run() {            int i = new Random().nextInt(100);            threadLocal.set(i);            System.out.println(threadLocal + "---" + Thread.currentThread().getName() + " set- " + i);            try {                Thread.sleep(2000);            } catch (InterruptedException e) {            }            System.out.println(threadLocal + "---" + Thread.currentThread().getName() + " get- " + threadLocal.get());        }    }    public static void main(String[] args) {        MyRunnable sharedRunnableInstance = new MyRunnable();        Thread thread1 = new Thread(sharedRunnableInstance);        Thread thread2 = new Thread(sharedRunnableInstance);        thread1.start();        thread2.start();    }}
程序的输出为:
java.lang.ThreadLocal@53545cc1---Thread-0 set- 18java.lang.ThreadLocal@53545cc1---Thread-1 set- 41java.lang.ThreadLocal@53545cc1---Thread-0 get- 18java.lang.ThreadLocal@53545cc1---Thread-1 get- 41

从输出可以看出,线程Thread-0和线程Thread-1同时操作同一个threadLocal变量java.lang.ThreadLocal@53545cc1,但它们的数据并没有出现错误,每个线程仍能获取到自己设置的数据。

下面从源码分析一下ThreadLocal是怎么实现的。

源码解析


set(value)方法的源码

public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);}
ThreadLocal的set方法中,首先获取到当前线程,然后通过getMap(Thread t)方法获取到当前线程中的ThreadLocalMap类型的threadLocals字段,getMap方法很简单,只有一句return t.threadLocals; 即获取到线程的threadLocals字段,在Thread类中该字段声明为:
ThreadLocal.ThreadLocalMap threadLocals = null;

然后把值和ThreadLocal一块儿放入到当前线程的ThreadLocalMap中。

到这里就可以理解Thread-0和Thread-1互不影响的原因:虽然threadLocal是同一个,但是在threadLocal的set方法中操作的是当前线程的ThreadLocalMap,即每个线程操作的是自己的ThreadLocalMap,并不是同一个对象,自然不会有数据的竞争。

get()方法和set方法是同样的原理,先获取到当前线程,然后获取到当前线程的threadLocals字段,然后得到在本线程中保存的值。


下面说一下ThreadLocalMap的实现

ThreadLocalMap的实现


ThreadLocalMap是ThreadLocal的静态内部类,ThreadLocalMap存储数据的结构为Entry类型的数组

private Entry[] table;
Entry是ThreadLocalMap的静态内部类,源码为:

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,创建了threadLocal的弱引用,用Object value存储设置的值。用弱引用是为了避免内存泄露,顺便说一下java中的引用:Java中有四类引用,从强到弱依次是强引用,软引用,弱引用和虚引用

  1. 强引用就是程序中常用的引用,例如:Object object = new Object();只要强引用存在,垃圾收集器永远不会回收掉被引用的对象。
  2. 软引用通过java.lang.ref.SoftReference使用,当内存充足时,不会回收软引用,当堆使用率临近阈值时,才会回收软引用的对象。
  3. 弱引用通过java.lang.ref.WeakReference使用,当GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。
  4. 虚引用java.lang.ref.PhantomReference,和没有引用几乎是一样的,随时都可能被垃圾收集器回收,试图通过虚引用的get()方法取得强引用时,总是会失败,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。

Entry[] table的大小必须是2的整数次幂。

ThreadLocalMap向Entry[] table中设置值的方法是set(ThreadLocal<?>key, Object value),其中的算法为:

通过threanLocal的threadLocalHashCode和table的长度计算出threadLocal要放在table的哪个位置

Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);//计算出threadLocal对应的数组下标
threadLocalHashCode是AtomicInteger(0).getAndAdd(0x61c88647),其实就是一个原子整数类不停地去加上0x61c88647,这是一个很特别的数,叫斐波那契散列(Fibonacci Hashing),将这个数作为哈希值的考量将会使哈希值均匀的分布在尺寸为 2 的 N 次方的数组里。
1、如果当前位置(tab[i])没有值,则在此位置赋值

tab[i] = new Entry(key, value);int sz = ++size;
2、如果当前位置已经有值了,则判断当前位置的值的key是否等于传进来的key,如果相等则把value赋给此位置的entry的value即可。

if (k == key) {      e.value = value;      return;}

3、如果当前位置有值,且值的key为null,则说明弱引用引用的对象已经被回收,这时需要手动的把entry设置为null,让垃圾收集回收,否则会造成内存泄露。因为table数组仍然保有对entry的引用,但是此时entry已经没用了。

if (k == null) {       replaceStaleEntry(key, value, i);//此方法中会把entry和entry.value都赋值为null,然后赋值为传进来的值       return;}

4、如果当前位置有值,但是值的key不等于传进来的key,说明放生了hash冲突,这里采用的解决办法是开放定址法(线性探测再散列),如果散列的位置可以就放在此位置,否则继续散列,直到找到合适的位置。

((i + 1 < len) ? i + 1 : 0)  //线性探测再散列代码


ThreadLocal总结

1、threadLocal操作的是每个线程自己的ThreadLocalMap,因此不会有线程的竞争。key是当前的threadlocal对象,value是当前线程放在这个threadlocal里面的值。

2、ThreadLocalMap是基于开放定址法(线性探测再散列实现的hash表,这里没有采用HashMap的数组加链表的实现方式是因为这里的场景决定了hash表中不会有太多的值,通过采用独特的斐波那契散列求hash值,可以极大的降低hash冲突概率,访问数据速度也比HashMap快。

3、ThreadLocalMap的实现类似WeakHashMap实现,都通过WeakReference封装了key值,防止内存泄漏;

4、ThreadLocalMap实现的挺复杂的,主要是为了避免内存泄露,加了好多处理过期数据的操作,这就在线程中添加了多余的操作。所以如果确定ThreadLocal没有用的话,可以调用ThreadLocal的remove()方法,这样就避免了在ThreadLocalMap中查找过期数据并处理的操作。

原创粉丝点击