常用方法
代码案例实现
(1) 不使用ThreadLocal时模拟多线程存取数据
public class ThreadLocalDemo1 {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
ThreadLocalDemo1 threadLocalDemo = new ThreadLocalDemo1();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
/**
* 每一个线程存一个变量,过一会取出这个变量
*/
threadLocalDemo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println("------------------------");
System.out.println(Thread.currentThread().getName() + "----->" + threadLocalDemo.getContent());
}
});
thread.setName("线程" + i);
thread.start();
}
}
}
结果:
------------------------
线程0----->线程4的数据
------------------------
线程4----->线程4的数据
------------------------
线程2----->线程4的数据
------------------------
线程3----->线程4的数据
------------------------
线程1----->线程4的数据
(2) 使用ThreadLocal对多线程进行数据隔离,把数据绑定到ThreadLocal
(传统解决方案首先想到的就是加锁,确实可以实现,但是却牺牲了效率,需要等待上一个线程之行结束才可以往下之行)
public class ThreadLocalDemo2 {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
private String content;
public String getContent() {
return threadLocal.get();
}
public void setContent(String content) {
threadLocal.set(content);
}
public static void main(String[] args) {
ThreadLocalDemo2 threadLocalDemo2 = new ThreadLocalDemo2();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
/**
* 每一个线程存一个变量,过一会取出这个变量
*/
threadLocalDemo2.setContent(Thread.currentThread().getName()+"的数据");
System.out.println("------------------------");
System.out.println(Thread.currentThread().getName() + "----->" + threadLocalDemo2.getContent());
}
});
thread.setName("线程" + i);
thread.start();
}
}
}
结果:
------------------------
------------------------
------------------------
线程3----->线程3的数据
------------------------
线程2----->线程2的数据
线程1----->线程1的数据
线程0----->线程0的数据
------------------------
线程4----->线程4的数据
二者都是用来处理多线程并发访问的问题,但是二者的原理和侧重点不一样,简要说就是,ThreadLocal牺牲了空间,而synchronized是牺牲了时间来保证线程安全(隔离)。
总结:在上述的案例当中,使用ThreadLocal更为合理,这样保证了程序拥有了更高的并发性。
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
// map不为空时,获取里面的Entry
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
// 返回结果
return result;
}
}
// 没有则赋值初始值null并返回
return setInitialValue();
}
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 不为空直接set
map.set(this, value);
} else {
// map为空则创建并set
createMap(t, value);
}
}
protected T initialValue() {
return null;
}
public void remove() {
// 获取当前线程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
// 移除ThreadLocalMap
m.remove(this);
}
}
private T setInitialValue() {
// 得到初始化值null
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程中ThreadLocalMap
ThreadLocalMap map = getMap(t);
// map存在的话把null设置进去,不存在则创建一个并将null设置进去
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
// 如果当前ThreadLocal属于TerminatingThreadLocal(关闭的ThreadLocal)则register(注册)到TerminatingThreadLocal
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
// Entry类,继承弱应用,为了和Thread的生命周期解绑
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
* 初始容量,必须是二的幂
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
* 根据需要调整大小。长度必须是2的幂。
*/
private Entry[] table;
/**
* The number of entries in the table.
* table中的entrie数量
*/
private int size = 0;
/**
* The next size value at which to resize.
* 要调整大小的下一个大小值
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
* 设置调整大小阈值以维持最坏的2/3负载因子
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
* 增量一
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
* 减量一
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化table
table = new Entry[INITIAL_CAPACITY];
// 计算索引在数组中的位置(核心代码)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 设置值
table[i] = new Entry(firstKey, firstValue);
size = 1;
// 设置阈值(INITIAL_CAPACITY的三分之二)
setThreshold(INITIAL_CAPACITY);
}
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
(1)这里定义了一个AtomicInteger,每次获取并加上HASH_INCREMENT(0x61c88647,这个值与斐波那契数(黄金分割)有关),是为了让哈希码能够均匀的分布在2的n次方的数组(Entry[])里面,也就尽可能避免了哈希冲突。
(2)hashcode & (INITIAL_CAPACITY - 1) 相当于hashcode % (INITIAL_CAPACITY - 1) 的高效写法,所以size必须为2的次幂,这样最大程度避免了哈希冲突。
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)]) {
// 获取到该Entry对应的ThreadLocal
ThreadLocal<?> k = e.get();
if (k == key) {
// key存在则覆盖value
e.value = value;
return;
}
// key为null但是值不为null,这说明了之前使用过,但是ThreadLocal被垃圾回收了,当前的Entry是一个陈旧的(Stale)元素
if (k == null) {
// key(ThreadLocal)不存在,则新Entry替换旧的Entry,此方法做了不少垃圾清理的动作,避免了内存泄漏。
replaceStaleEntry(key, value, i);
return;
}
}
// ThreadLocal中未找到key也没有陈旧的元素,此时则在这个位置新创建一个Entry
tab[i] = new Entry(key, value);
int sz = ++size;
// cleanSomeSlots用于清理e.get()为null的key,如果大于阈值(2/3容量)则rehash(执行一次全表扫描清理工作)
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* 线性探测法查找元素,到最后一个时重定位到第一个
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}