首先我们需要了解什么是ThreadLocal。ThreadLocal是一个与线程相关的类,它提供了线程本地存储(ThreadLocal Storage)功能,也就是说,对于同一个ThreadLocal实例,每个线程都可以获取相同但是独立的值。这样,多个线程之间可以相互独立,不会互相冲突,实现了数据的隔离。
一、ThreadLocal如何实现线程本地存储的
在讲ThreadLocal的源码之前,我们先看看ThreadLocal如何实现线程本地存储的。ThreadLocal内部维护了一个Map,这个Map的key是Thread,value是该线程的本地变量值。当我们调用ThreadLocal.get()方法时,ThreadLocal首先获取当前线程,然后将当前线程作为key去Map中查找对应的value,如果存在,直接返回该value;否则,调用ThreadLocal.initialValue()方法来创建一个初始的本地变量值,并将这个变量与当前线程关联起来。这个关联使用了WeakReference来实现,而WeakReference可以有效防止内存泄露。
ThreadLocal.set()方法实际上是调用了ThreadLocalMap.set(ThreadLocal, Object)方法来实现的。ThreadLocalMap是ThreadLocal的内部类,它继承了WeakReference,并且重写了finalize()方法,用来删除这个键值对。
ThreadLocalMap的put()方法是完全的线程不安全的,因为可能出现多个线程同时进行线性探测,导致数据覆盖。所以,ThreadLocalMap在实现时使用了“开放地址法”(open addressing)来解决hash冲突。
二、ThreadLocal源码详解
1、ThreadLocal的属性和构造函数
public class ThreadLocal<T> {
/**
* The next hash code to be given out. Updated atomically. Start with
* a random value to avoid collisions with default hash codes of 0.
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* The difference between successively generated hash codes, approximately
* golden ratio multiplied by 2^32.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Creates a new thread-local variable.
*/
public ThreadLocal() {
}
/**
* Returns the value in the current thread's copy of this thread-local
* variable. If the variable has no value for the current thread, it is
* first initialized to the value returned by an invocation of the
* {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
...
}
/**
* Sets the current thread's copy of this thread-local variable to the
* specified value. Most subclasses will have no need to override this
* method, relying solely on the {@link #initialValue} method to set the
* values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
...
}
/**
* Removes the current thread's value for this thread-local variable.
*/
public void remove() {
..
}
/**
* Computes the initial value for this thread-local variable. This
* method is called once per accessing thread for each thread-local,
* the first time each thread accesses the variable with {@link #get}
* or {@link #set}.
*
* <p>This implementation simply returns <tt>null</tt>; if the
* programmer desires thread-local variables to have an initial value
* other than <tt>null</tt>, <tt>ThreadLocal</tt> must be subclassed,
* and this method overridden. Typically, an anonymous inner class
* will be used.
*
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}
}
ThreadLocal主要需要了解的是它的两个方法,一个是get()方法,一个是set()方法。这两个方法是ThreadLocal的核心功能,也是我们平常使用最多的两个方法。然后我们看到ThreadLocal内部有一个threadLocalHashCode属性,它是用来做什么的呢?这个属性是为了应对多个ThreadLocal变量在一个线程中使用时产生的冲突问题的,每一个ThreadLocal对象都会有自己的唯一的threadLocalHashCode来表示。再看到静态变量nextHashCode,这个变量是用来给threadLocalHashCode赋值的,每次访问ThreadLocal都会给threadLocalHashCode赋值。需要说明的是,nextHashCode是线程安全的,使用了AtomicInteger来实现。
2、ThreadLocal的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的初始值方法,该方法默认返回null
}
ThreadLocal的get()方法首先获取当前线程,然后通过当前线程获取到它对应的ThreadLocalMap,如果Map存在并且查找到了当前ThreadLocal的Entry,则直接返回该Entry中存储的值。如果Map不存在,则调用setInitialValue()方法来设置默认值,即调用ThreadLocal的initialValue()方法。
3、ThreadLocal的set()方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // 调用ThreadLocalMap的set方法将ThreadLocal关联到当前线程的Map中
else
createMap(t, value); // 如果Map不存在就直接创建ThreadLocalMap并保存值
}
ThreadLocal的set()方法首先获取当前线程,然后通过当前线程获取到它对应的ThreadLocalMap,如果Map存在,则在Map中设置当前ThreadLocal对应的值;如果Map不存在,则创建一个Map,并在其中设置当前ThreadLocal对应的值。其中,ThreadLocalMap的set()方法是完全的线程不安全的,因为可能出现多个线程同时进行线性探测,导致数据覆盖。所以,ThreadLocalMap在实现时使用了“开放地址法”(open addressing)来解决hash冲突。
4、ThreadLocal示例1
public class ThreadLocalTest1 {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
static class MyRunnable implements Runnable {
@Override
public void run() {
int myCount = threadLocal.get();
System.out.println(Thread.currentThread().getName() + " : " + myCount);
threadLocal.set(myCount + 1);
myCount = threadLocal.get();
System.out.println(Thread.currentThread().getName() + " : " + myCount);
threadLocal.set(myCount + 1);
myCount = threadLocal.get();
System.out.println(Thread.currentThread().getName() + " : " + myCount);
threadLocal.remove(); // 删除 threadLocal
}
}
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
这个例子创建了一个ThreadLocal变量和一个Runnable线程。我们通过Runnable中的get()方法获取ThreadLocal的值,然后将它打印出来;接着,通过set()方法给它赋新值,并再次打印出来。使用remove()方法可以删除这个ThreadLocal变量。
5、ThreadLocal示例2
public class ThreadSafeFormatter {
public static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static String format(Date date) {
return DATE_FORMAT.get().format(date);
}
}
这是一个线程安全的日期格式化类,使用了ThreadLocal变量。通过ThreadLocal.withInitial方法来创建ThreadLocal变量,这个方法会返回一个Supplier对象,然后我们调用get()方法就可以获取到一个SimpleDateFormat对象。然后,在format()方法中使用ThreadLocal.get()方法获取SimpleDateFormat对象来进行日期格式化。
以上就是Java并发编程学习之ThreadLocal源码详析的完整攻略,希望能够对你有所帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java并发编程学习之ThreadLocal源码详析 - Python技术站