深入理解ThreadLocal
来源:互联网 发布:王者荣耀数据封神榜67 编辑:程序博客网 时间:2024/06/05 18:48
ThreadLocal是什么:
- ThreadLocal翻译过来是本地线程,但它却不是线程,只是保存线程的自己使用的变量
- ThreadLocal是线程封闭的一种实现,什么是线程封闭呢,线程封闭就是将某个对象封闭在一个线程中,使用这种方式将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。假如你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。就不会产生竞态状态了
简单例子
一个实体类,里面有一个Threadlocal类
只能通过get和set方法去访问该对象的值
public class MyNumber { private static ThreadLocal<Integer> number = new ThreadLocal<Integer>() { protected Integer initialValue() { return 0; } }; public Integer getNext() { number.set(number.get() + 1); return number.get(); } public static void main(String[] args) throws InterruptedException { MyNumber myNumber = new MyNumber(); ExecutorService es = Executors.newCachedThreadPool(); for (int i = 0; i < 2; i++) { es.execute(new MyThread(myNumber)); } TimeUnit.SECONDS.sleep(3); es.shutdownNow(); }}
线程类:MyThread,其里面有一个属性,MyNumber,在run方法里,对LocalThread里保存的值其进行加1操作
public class MyThread implements Runnable { private MyNumber myNumber; public MyThread(MyNumber myNumber) { this.myNumber = myNumber; } @Override public void run() { for(int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "-number: " + myNumber.getNext()); } }}
输出结果:
pool-1-thread-2-number: 1pool-1-thread-1-number: 1pool-1-thread-2-number: 2pool-1-thread-2-number: 3pool-1-thread-2-number: 4pool-1-thread-2-number: 5pool-1-thread-1-number: 2pool-1-thread-1-number: 3pool-1-thread-1-number: 4pool-1-thread-1-number: 5
创建了两个线程,都将调用mynumber的getNext()方法,但两个线程的操作互不影响。
从输出结果可以看出:确实为每个线程通过ThreadLocal为每个线程创建了一个Integer的存储区域,通过set和get去设置和获得这个值。
ThreadLocal是如何实现线程封闭的
源码中,ThreadLocal是泛型类的,每个线程都将会为这个T类型的值创建存储区域
构造函数:是一个默认构造函数
public ThreadLocal() {}
在MyNumber类中,初始化ThreadLocal时重写了initialValue()方法,因为该方法默认返回null
protected T initialValue() { return null; }
对于基本类型包装类我们肯定不想返回值是null,所以进行了重写
get方法
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();}
在获取值的时候,首先要获得当前的线程,以便确定是针对哪个线程获取其中的ThreadLocal保存的值。通过getMap方法获取键值对,获得其中的value值,如果值不等于null,则进行强制转化以后返回,为空,则对其进行初始化一次,setInitialValue()的代码如下
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
其初始化的值仍为initialValue()方法返回的值,这里如果map为空,则会创建一个集合。
getMap方法:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
Thread类中:
ThreadLocal.ThreadLocalMap threadLocals = null;
说明每一个线程类中都有threadLocals,getMap方法获得当前线程的的threadsLocals的值,是一个ThreadLocalMap类
重点讲解一下ThreadLocal中的Map操作。
讲Map的时候先讲一下ThreadLocalMap的静态内部类Entry,继承了WeakReference,在无法使用它时,垃圾收集器会将其回收。里面有一个value的属性,用于保存线程的值。
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
它的super是WeakReference,最终将调用Reference这个类的构造函数。这个类主要是用来为Entry设置一个value值,并调用Refernce构造函数。
获得Entry的方法也是通过i
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } }
ThreadLocalMap是ThreadLocal的静态内部类
其中一个构造函数:
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); } }
首先确定了Entry[]数组的大小,通过与运算获得value存储在数组中的第几p的实现方式有点类似
get方法中首先通过getMap方法获得对应的ThreadLocalMap,并通过getEntry()方法获得Entry对象,从而获得value的值
- 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); }
set方法里的核心方法应该是ThreadMap类中的set方法
private void set(ThreadLocal<?> key, Object value) { 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(); }
该段的意思是首先通过计算hash并做与运算,计算出存储位置,通过存储位置获得value,如果value不为空,则重置value的值,否则table[i]的位置则为Entry
因为getMap获取的是当前线程的threadLocals,其初始时值为0,所以set时,先调用 createMap(t, value)方法:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this,firstValue); }
为当前线程的threadLocals进行初始化,传入了一个threadLocal对象和初始化值。
- 总结
ThreadLocal实现为使用相同变量的每个线程创建一个存储空间是通过Thread类的threadLocals实现的。因为每个线程都有一个ThreadLocal.ThreadLocalMap,所以可以做到独立存储。当调用set方法时,首先会获得当前线程类的threadLocals并将其存储到map引用中,第一次调用set时,因为threadLocals初始化值为null,所以先调用creatMap(t,value)方法初始化当前线程的threadLocals(初始化做了什么:通过一个类似于hashMap实现存储的计算获取value在Entry数组中的存储位置),所以当第二次调用set方法时,threadLocals不为空,将调用map.set(this, value)方法,如果通过计算原来Entry[i]上有值,则将替换这上面的值,否则就进行赋值。
对于get方法,首先通过获得getMap()方法获得threadLocals的值,一般之前会进行set方法的调用,map不为null,通过getEntry()方法返回保存value的Entry对象,从而获得value值,如果未为进行set方法的调用,则会返回initialValue()方法返回的初始化值。总之,保存和获取都是通过threadLocal实现的。可以将ThreadLocal视为一个Thread-T键值对象,但实际上保存在Thread对象中,当线程终止以后,这些值i将会作为垃圾回收,因为保存value的Entry类继承了WeakReference
- 深入理解ThreadLocal
- ThreadLocal深入理解
- ThreadLocal深入理解2
- ThreadLocal深入理解
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- ThreadLocal深入理解
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- ThreadLocal深入理解 修订版
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 【Java】深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- jsoup获取天气
- HTML5-增强的页面元素
- 米勒电容引起的米勒效应
- ChecklistBox的使用总结
- 抽象类
- 深入理解ThreadLocal
- @Scheduled用法
- Codeforces Round #436 (Div. 2)
- 基于神经网络的水下机器人运动预测控制方法-读后总结
- 统计学术语及解释(二)
- 二进制
- 二进制运算
- UVA 12096 The SetStack Computer(stack及其它STL容器综合使用)
- [Dubbo]dubbo只订阅不注册+直连提供者