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,但它们的数据并没有出现错误,每个线程仍能获取到自己设置的数据。
源码解析
set(T 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,并不是同一个对象,自然不会有数据的竞争。
T 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中有四类引用,从强到弱依次是强引用,软引用,弱引用和虚引用
- 强引用就是程序中常用的引用,例如:Object object = new Object();只要强引用存在,垃圾收集器永远不会回收掉被引用的对象。
- 软引用通过java.lang.ref.SoftReference使用,当内存充足时,不会回收软引用,当堆使用率临近阈值时,才会回收软引用的对象。
- 弱引用通过java.lang.ref.WeakReference使用,当GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。
- 虚引用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中查找过期数据并处理的操作。
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal 详解
- ThreadLocal 详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- ThreadLocal详解
- JSON的方法及数据绑定
- react 资源网址
- webstorm工具使用的快捷键
- 我心中的未来购物中心
- mybatis 关于 like %% 查询 mysql oracle sqlserver 数据库
- ThreadLocal详解
- 残
- CF 373D Counting Rectangles is Fun 单调栈+DP
- Java提高篇(1)--JAVA中StringBuffer类常用方法详解
- Django的views编写以及结合mysql存库小demo
- Linux命令小技巧
- 积累学习Kotlin进行开发遇到的错误
- tcp/ip协议栈--socket API 之socket()
- 深入浅出讲解:php的socket通信