Java多线程-理解ThreadLocal
此文章是参考许多博客写出来的,如果有不清楚的请往这些博客详细查看
ThreadLocal作用
ThreadLocal不是用来解决共享对象的多线程访问问题的,通过ThreadLocal的set()方法设置到线程的ThreadLocal.ThreadLocalMap里的是是线程自己要存储的对象,其他线程不需要去访问,也是访问不到的。各个线程中的ThreadLocal.ThreadLocalMap以及ThreadLocal.ThreadLocal中的值都是不同的对象
ThreadLocal初始化
当我们执行new ThreadLocal时执行的步骤
1、步骤一
private final int threadLocalHashCode = nextHashCode();//堆当中
2、步骤二
private static int nextHashCode(){
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
3、在上面两个步骤执行的时候,如果我们的类是第一创建,那么我们首先会执行下面的两个操作,并将类变量和常量放到方法区当中
private static AtomicInteger nextHashCode =new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
4、
/**
* 为什么每一次new 一个ThreadLocal的threadLocalHashCode 值都会增加HASH_INCREMENT
* 这是因为当我们在初始化一个ThreadLocal的时候我们必须首先初始化我们的final类型的变量threadLocalHashCode
* 然后会调用nextHashCode(),我们的nextHashCode变量是一个static类型的变量,
* 也就是说我们每一次都会对nextHashCode=nextHashCode+HASH_INCREMENT,然后就会nextHashCode这个变量是所有类都共享的,保存在方法区当中
*/
//实例变量
private final int threadLocalHashCode = nextHashCode();
//类变量
private static AtomicInteger nextHashCode = new AtomicInteger();//执行步骤1
//常量
private static final int HASH_INCREMENT = 0x61c88647;//执行步骤2
//静态方法
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//构造方法
public ThreadLocal() {
}
自己写的一个测试
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by luckyboy on 2018/7/6.
*/
public class ThreadLocalTest_I {
private final int threadLocalHashCode = nextHashCode();//堆当中
private final static int HASH_INCREMENT = 0x61c88647;//内存位置:方法区
private static AtomicInteger nextHashCode = new AtomicInteger();//内存位置:方法区
private static int nextHashCode(){
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
public int getThreadLocalHashCode(){
return this.threadLocalHashCode;
}
public static void main(String[] args){
ThreadLocalTest_I threadLocal = new ThreadLocalTest_I();
ThreadLocalTest_I threadLocal1 = new ThreadLocalTest_I();
System.out.println(threadLocal.getThreadLocalHashCode());
System.out.println(threadLocal1.getThreadLocalHashCode());
}
}
输出结果
0
1640531527
ThreadLocal.get
get()
getMap(Thread t)
setInitialValue()
initialValue()
createMap(Thread t, T firstValue) {
1、获取当前线程并获取线程的ThreadLocalMap类型的map对象
2、判断map、Entry是否为空,如果二者不为空,那么我们就根据ThreadLocal对象在map中找到对应的result返回;二者如果有一个为空那么转到步骤3
3、setInitialValue()方法会再一次判断map是否为空,如果不为空则表示我们的Entry为空,添加键值对(Key 是我们传递进来的ThreadLocal对象-隐式参数this,value是传递进来的显示参数),如果map为空,那么我们转到步骤4
4、createMap(Thread t, T firstValue)为当前的线程创建一个ThreadLocalMap并将ThreadLocal-firstValue添加到ThreadLocalMap中。返回一个ThreadLocalMap,然后setInitialValue就会返回一个value=null的值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) { //map != null && e != null返回一个value
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //map == null || e == null返回一个null
}
在上面的map为空和Entry为空都有会调用该setInitialValue()
private T setInitialValue() {
T value = initialValue();//调用initialValue()返回一个null值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) //如果map存在则之间添加ThreadLocal-null
map.set(this, value);
else // 如果不存在则为当前的Thread创建一个ThreadLocalMap对象
createMap(t, value);
return value; //null
}
//
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal.set
1、执行getMap()获取currentThread的ThreadLocalMap对象map
2、判断map是否为空,map如果不为空,则添加隐式参数this代表的当前的ThreadLocal对象和对应的value值到map中,如果为空则到步骤3
3、为当前线程创建一个ThreadLocalMap对象,然后添加ThreadLocal-value;
//getMap获取当前线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//set方法为当前Thread添加一个ThreadLocal-value到ThreadLocalMap当中
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//currentThread的ThreadLocalMap为空则创建一个map,添加ThreadLocal-value
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal.ThreadLocalMap
ThreadLocalMap是ThreadLocal中的一个静态内部类,当我们要往一个线程中添加一个ThreadLocal时,我们首先会获取当前线程的ThreadLocalMap,然后给将ThreadLocal作为一个键添加到线程的ThreadLocalMap中。
ThreadLocal采用的是线性探查法,也就是开地址寻址法的方式去根据ThreadLocal是否存在ThreadLocalMap中
首先我们来看一下ThreadLocalMap中的数据结构和重要的参数
//map中的元素Entry就是我们常说的key-value形式
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//map采用数组的方式存储我们的Entry,这里并不是采用拉链法解决hash冲突问题
private Entry[] table;
//map中初始数组的大小,必须是2的次幂
private static final int INITIAL_CAPACITY = 16;
ThreadLocal的构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];//创建一个大小为16的Entry数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//
table[i] = new Entry(firstKey, firstValue); //添加key-value
size = 1; //初始化map中Entry的数量
setThreshold(INITIAL_CAPACITY);//设置rehash的阈值为16
}
其中最重要的一条是我们如果将我们的ThreadLocal放到Map中的Entry[]那个位置
/**
*不管firstKeyfirstKey.threadLocalHashCode的值有多大,
*和INITIAL_CAPACITY-1进行&操作后,范围都是在0~INITIAL_CAPACITY-1
*/
firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
我们看一下getEntry()
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1); //根据ThreadLocal.threadLocalHashCode确定ThreadLocal在Entry中的位置
Entry e = table[i];
if (e != null && e.get() == key) // 如果Entry存在且Entry的Key和ThreadLocal.threadLocalHashCode相等,表明找到了ThreadLocal对应的Entry,此时返回Entry
return e;
else
return getEntryAfterMiss(key, i, e);//如果Entry == null 或者 Entry对应的key和ThreadLocal.threadLocalHashCode不相等,则调用此方法法
}
//getEntryAfterMiss(key, i, e)
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)//如果对应的Entry不为空,且this.ThreadLocal == e.get(),那么此时返回Entry
return e;
if (k == null)//如果对应的Entry的key为null,那么此时调
expungeStaleEntry(i);
else//如果上面两个步骤都执行完了,表明this.ThreadLocal.threadLocalHashCode确实存在与表中,但是是其他ThreadLocal具有相同的ThreadLocalHashCode的位置
i = nextIndex(i, len);那么这个时候就需要进行下一个位置查找了
e = tab[i];
}
return null;
}
看一下
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;
}
ThreadLocal实例
import java.util.concurrent.TimeUnit;
/**
* Created by luckyboy on 2018/7/6.
*/
public class ThreadLocalTest_II {
public static void main(String[] args){
UnsafeTask task = new UnsafeTask();
//SafeTask task = new SafeTask();
for(int i = 0;i<10 ;i++){
Thread thread = new Thread(task);
thread.start();
try {
TimeUnit.SECONDS.sleep(2);
//Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class UnsafeTask implements Runnable{
private Date startDate;
@Override
public void run() {
startDate = new Date();
System.out.printf("Starting Thread:%s : %s\n",Thread.currentThread().getId(),startDate);
try {
TimeUnit.SECONDS.sleep((long)Math.rint(Math.random()*10));
System.out.printf("Thread Finished:%s : %s\n",Thread.currentThread().getId(),startDate);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出
Starting Thread:11 : Fri Jul 06 20:26:53 CST 2018
Starting Thread:12 : Fri Jul 06 20:26:55 CST 2018
Starting Thread:13 : Fri Jul 06 20:26:57 CST 2018
Thread Finished:12 : Fri Jul 06 20:26:57 CST 2018
Starting Thread:14 : Fri Jul 06 20:26:59 CST 2018
Thread Finished:13 : Fri Jul 06 20:26:59 CST 2018
Starting Thread:15 : Fri Jul 06 20:27:01 CST 2018
Thread Finished:11 : Fri Jul 06 20:27:01 CST 2018
Starting Thread:16 : Fri Jul 06 20:27:03 CST 2018
Thread Finished:14 : Fri Jul 06 20:27:03 CST 2018
Starting Thread:17 : Fri Jul 06 20:27:05 CST 2018
Starting Thread:18 : Fri Jul 06 20:27:07 CST 2018
Thread Finished:17 : Fri Jul 06 20:27:07 CST 2018
Thread Finished:15 : Fri Jul 06 20:27:07 CST 2018
Starting Thread:19 : Fri Jul 06 20:27:09 CST 2018
Thread Finished:18 : Fri Jul 06 20:27:09 CST 2018
Starting Thread:20 : Fri Jul 06 20:27:11 CST 2018
Thread Finished:16 : Fri Jul 06 20:27:11 CST 2018
Thread Finished:19 : Fri Jul 06 20:27:11 CST 2018
Thread Finished:20 : Fri Jul 06 20:27:11 CST 2018
再看看我们的Date对象放在ThreadLocal当中
public class ThreadLocalTest_II {
public static void main(String[] args){
UnsafeTask task = new UnsafeTask();
//SafeTask task = new SafeTask();
for(int i = 0;i<10 ;i++){
Thread thread = new Thread(task);
thread.start();
try {
TimeUnit.SECONDS.sleep(2);
//Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class SafeTask implements Runnable{
/***
* 这一刻发生了什么,当我们创建一个线程的时候我们首先会先创建一个ThreadLocal类,
* ThreadLocal 重写了initialValue(),我们当我们调用get()方法的时候会用到initialValue(),然后返回initialValue方法的值
*/
private static ThreadLocal<Date> startDate = new ThreadLocal<Date>(){
protected Date initialValue(){
return new Date();
}
};
@Override
public void run() {
System.out.printf("Starting Thread:%s : %s\n",Thread.currentThread().getId(),startDate.get());
try {
TimeUnit.SECONDS.sleep((long)Math.rint(Math.random()*10));
System.out.printf("Thread Finished:%s : %s\n",Thread.currentThread().getId(),startDate.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果
Starting Thread:11 : Fri Jul 06 20:32:05 CST 2018
Starting Thread:12 : Fri Jul 06 20:32:07 CST 2018
Starting Thread:13 : Fri Jul 06 20:32:09 CST 2018
Thread Finished:11 : Fri Jul 06 20:32:05 CST 2018
Starting Thread:14 : Fri Jul 06 20:32:11 CST 2018
Thread Finished:12 : Fri Jul 06 20:32:07 CST 2018
Starting Thread:15 : Fri Jul 06 20:32:13 CST 2018
Starting Thread:16 : Fri Jul 06 20:32:15 CST 2018
Thread Finished:13 : Fri Jul 06 20:32:09 CST 2018
Starting Thread:17 : Fri Jul 06 20:32:17 CST 2018
Thread Finished:14 : Fri Jul 06 20:32:11 CST 2018
Thread Finished:17 : Fri Jul 06 20:32:17 CST 2018
Starting Thread:18 : Fri Jul 06 20:32:19 CST 2018
Starting Thread:19 : Fri Jul 06 20:32:21 CST 2018
Thread Finished:15 : Fri Jul 06 20:32:13 CST 2018
Thread Finished:18 : Fri Jul 06 20:32:19 CST 2018
Starting Thread:20 : Fri Jul 06 20:32:23 CST 2018
Thread Finished:19 : Fri Jul 06 20:32:21 CST 2018
Thread Finished:16 : Fri Jul 06 20:32:15 CST 2018
Thread Finished:20 : Fri Jul 06 20:32:23 CST 2018
结果分析:
参考文献
http://www.cnblogs.com/xrq730/p/4854820.html
https://www.cnblogs.com/dolphin0520/p/3920407.html
http://www.iteye.com/topic/103804
http://ifeve.com/thread-management-10/