前面写了一些关于JUC包下的一些主要常见类的源码分析,下面开始一段新的篇章,其他多线程开发设计到的一些方式,首先是ThreadLocal的源码分析。
声明:基于JDK1.8源码进行分析的。
用途上来讲,一般情况下,通过ThreadLocal.set(Object obj) 到线程中的对象obj是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。其实简单来讲,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。所以这么来看,它适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’,synchronized顺序执行是‘时间换取空间’。
类继承结构
public class ThreadLocal<T>
没有继承也没有实现。
构造方法
ThreadLocal()
public ThreadLocal() {
}
默认构造方法。构造方法有且只有一个。
内部类
ThreadLocalMap
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//more code ...
这是ThreadLocal的一个内部类,从源码来看,里面确实还有一个内部类Entry。 内部有一个成员属性:
private Entry[] table;
可以想到是通过对象数组实现的。下面我们看一下他的构造方法。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//private Entry[] table;
//private static final int INITIAL_CAPACITY = 16;
//初始化数组,大小默认为16
table = new Entry[INITIAL_CAPACITY];
//key的hashcode & 数组长度作为下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//数组存入对应的对象
table[i] = new Entry(firstKey, firstValue);
//更新size大小
size = 1;
//设置阈值
//private static final int INITIAL_CAPACITY = 16;
setThreshold(INITIAL_CAPACITY);
}
内部Entry定义如下:
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,也就是弱引用,我们不禁联想到强引用,软引用,虚引用,和弱引用,这个已经在前面介绍过,此次不再赘述。如果entry.get()==null,意味着key不在引用,因此在table中的键值对就会被去除。
核心方法
get()
public T get() {
//记录当前线程
Thread t = Thread.currentThread();
//调用内部getMap,后续分析
ThreadLocalMap map = getMap(t);
//检查是否为空,
//如果不为空,则取出value即可,
//如果为空,则说明此ThreadLocal随关联的键值对被垃圾回收了,
//这里就再一次的调用了setInitialValue()方法进行初始化
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
getMap(Thread t)
得到当前线程的ThreadLocalMap对象。
//获取对应的map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
setInitialValue()
如果没有获取到map,也就是map为空的时候会调用该方法。
private T setInitialValue() {
T value = initialValue();
//记录当前线程
Thread t = Thread.currentThread();
//获取对应的map
ThreadLocalMap map = getMap(t);
//如果map不为空,直接存入对应的值
if (map != null)
map.set(this, value);
else
//为空,在创建一个新的map
createMap(t, value);
//返回value值
return value;
}
//默认返回null
//一般情况下,这个方法至多被每个线程调用一次。
//但是,在调用remove方法之后紧跟着调用了get方法则会又一次的调用此方法。
protected T initialValue() {
return null;
}
//重新初始化一个map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
下面我们看一下map不为空的时候是怎么实现的。get()方法中源码如下:
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
可以看到是调用map的getEntry方法。源码如下:
private Entry getEntry(ThreadLocal<?> key) {
//计算位置
int i = key.threadLocalHashCode & (table.length - 1);
//获取数组中对应位置元素
Entry e = table[i];
//如果e不为空,且key相等,返回对应的Entry对象
if (e != null && e.get() == key)
return e;
else
//e为空或者key不相等调用该方法
return getEntryAfterMiss(key, i, e);
}
//参数:key, i, e
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
//记录当前数组
Entry[] tab = table;
//数组长度
int len = tab.length;
//死循环
//如果e为空,直接返回
//如果不为空,则是key相等
while (e != null) {
//获取e对应的key
ThreadLocal<?> k = e.get();
//这里只是为了增加命中的几率
if (k == key)
//返回e
return e;
//e的key为空,情况处理
if (k == null)
//调用该方法
expungeStaleEntry(i);
//e存在key,但是和传入的参数key不相等
else
//调用该方法,更新i
i = nextIndex(i, len);
//遍历数组
e = tab[i];
}
return null;
}
//根据i和len计算下一个索引
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
//key为空的时候,调用该方法
//参数为:i
//被GC回收
private int expungeStaleEntry(int staleSlot) {
//记录当前数组
Entry[] tab = table;
//记录当前数组长度
int len = tab.length;
// expunge entry at staleSlot
//tab[i].value=null 相当于将i位置的元素置空
tab[staleSlot].value = null;
//然后将tab[i]位置也置为空
tab[staleSlot] = null;
//更新维护size
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;
}
上面就是整个get方法的调用过程,下面我们看一下set方法的实现。
set(T value)
public void set(T value) {
//记录当前线程
Thread t = Thread.currentThread();
//获取map
ThreadLocalMap map = getMap(t);
//map不为空,直接调用set
if (map != null)
map.set(this, value);
else
//初始化
createMap(t, value);
}
通过源码我们看到调用的方法,getMap方法就不在重复介绍了,我们看一下map的set方法的实现。
//参数:this, value
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
//记录当前数组
Entry[] tab = table;
//记录当前数组长度
int len = tab.length;
//计算数组中的位置
int i = key.threadLocalHashCode & (len-1);
//对应位置的元素e
//e不为空的时候执行循环
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//e对应的key
ThreadLocal<?> k = e.get();
//如果循环的e的key和参数的key相等
if (k == key) {
//更新value,直接返回
e.value = value;
return;
}
//key为空,直接释放
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//执行到这里说明没有找到对应的e,且找到对应的位置
//创建一个新的Entry,并存入
tab[i] = new Entry(key, value);
//更新维护size
int sz = ++size;
//是否需要扩容处理
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
上面方法中,如果发生hash碰撞,也就是冲突了,那么采用的则是nextIndex,往后一个个寻找到第一个为空的空位,然后存放对应的元素即可。下面我们看一下remove方法的实现。
remove(ThreadLocal<?> key)
用来移除对应的元素。
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;
}
}
}
下面看一个DEMO演示。
package demo;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ThreadLocal测试例程
* @ClassName: ThreadLocalDemo
* @Description: TODO
* @author BurgessLee
* @date 2019年5月6日
*
*/
public class ThreadLocalDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
int initVal = atomicInteger.getAndIncrement();
System.out.println("initVal= " +initVal);
return initVal;
}
};
for(int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +",value="+ threadLocal.get());
}
},"thread"+(i+1)).start();
}
}
}
输出结果:
initVal= 1
initVal= 3
thread2,value=1
initVal= 0
thread1,value=0
thread4,value=3
initVal= 4
thread5,value=4
initVal= 2
thread3,value=2
以上就是所有源码分析和演示过程了,主要是对核心方法进行了主要的分析,如果有不对的地方还请指正。