ThreadLocal是什么?
对java多线程有了解的人都清楚,在多个线程程序中,会出现线程安全性问题,即多线程是不安全的,线程不安全的场景有很多,这里不一一赘述,这里重点是多线程不安全的场景之一————类成员变量在多线程环境下是不安全的。举例如下:
package testpackage;
import lombok.Data;
@Data
public class Class2 {
private int i;
private ThreadLocal<Integer> j = new ThreadLocal<>();
}
//------------------------万恶的分割线----------------------------------------
package testpackage;
public class Class1 {
public static void main(String[] args) {
Class2 class2 = new Class2();
class2.setI(1);
new Thread(()->{
System.out.println("子线程开始");
class2.setI(10);
System.out.println("子线程 i=" + class2.getI());
System.out.println("子线程结束");
}).start();
System.out.println("主线程休眠3秒");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线 i ="+class2.getI());
System.out.println("主结束");
}
}
int类型的i为Class2的类成员变量,Class1里有两个线程,一个为主线程,一个为从主线程新开的子线程,假定两个线程都需要使用i,理想状态是:主线程与子线程互不干扰,即变量i在两个线程中应该是独立互不干扰的(就好像人民币是文明社会人类共有的概念【定义的变量i】,但是每个人拥有的人民币却是他们自己的),上述代码运行结果如下:
主线程休眠3秒
子线程开始
子线程 i=10 --重点
子线程结束
主线 i =10 --重点
主结束
发现了么?主线程的 i 结果为10,按理想状态,变量 i 在子线程中改为了10,但这个改动应与主线程无关,因为这是两个线程,两个线程间的应该互不干扰才是线程安全的,但上述代码实际运行情况是子线程修改了 i 的值后,对主线程的 i的使用产生了影响。
上述现象既是开篇提到的 类成员变量在多线程环境下是不安全的
那么,问题来了,我如果需要使用线程安全的变量该怎么办呢?这个问题的答案就是————ThreadLocal,使用ThreadLocal类型的变量即可使其线程安全,具体使用方法如下:
package testpackage;
import lombok.Data;
@Data
public class Class2 {
private int i;
private ThreadLocal<Integer> j = new ThreadLocal<>();
}
//------------------------万恶的分割线----------------------------------------
package testpackage;
public class Class1 {
public static void main(String[] args) {
Class2 class2 = new Class2();
//主线程设置 i 的值为1
class2.setI(1);
//主线程设置 j 的值为1
class2.getJ().set(1);
new Thread(()->{
System.out.println("子线程开始");
class2.setI(10);
class2.getJ().set(11);
System.out.println("子线程 i=" + class2.getI());
System.out.println("子线程 j=" + class2.getJ().get());
System.out.println("子线程结束");
}).start();
System.out.println("主线程休眠3秒");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线 i ="+class2.getI());
System.out.println("子线程 j=" + class2.getJ().get());
System.out.println("主结束");
}
}
上述代码的运行情况如下:
主线程休眠3秒
子线程开始
子线程 i=10
子线程 j=11
子线程结束
主线 i =10
主线程 j=1
主结束
结果证明,ThreadLocal的使用保证了变量在多线程中的安全性。
那么,ThreadLocal是如何实现变量线程间的安全性的呢,是如何保证变量在多个线程间独立互不干扰呢?现在,就开始对ThreadLocal的实现原理逐一分析:
首先,从Thread说起:
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
由此,可以得到ThreadLocal内部结构图如下:
从上面的结构图,我们已经窥见ThreadLocal的核心机制:
- 每个Thread线程内部都有一个ThreadLocalMap。
- ThreadLocalMap里面存储ThreadLocal对象(key)和线程的变量副本(value)
- 但是,Thread内部的ThreadLocalMap是由ThreadLocal维护的,由ThreadLocal负责向ThreadLocalMap获取和设置线程的变量值。
所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
ThreadLocal的主要方法解析:
1.ThreadLocal主要有以下方法
public T get()
public void set(T value)
public void remove()
- get()方法用于获取当前线程的副本变量值。
- set()方法用于保存当前线程的副本变量值。
- remove()方法移除当前前程的副本变量值。
我们先来看看 get 方法:
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
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;
}
}
}
private Entry[] table;
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);
}
}
}
//---------------------------------------万恶的分割线----------------------------------------------
/**
get方法就做了如下几件事:
1.获取当前线程
2.拿到当前线程Thread对象后,拿到线程类成员threadLocals
3.调用getEntry以ThreadLocal
**/
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}