ThreadLocal内部维护的是一个类似Map的ThreadLocalMap数据结构,key为当前对象的Thread对象,值为泛型的Object。使用ThreadLocal比较方便的就是当thread不变的情况下,可以很方便的设置或者获取对象。但是需要注意的是ThreadLocal中的Entry的key和value的关系有系统进行维护,若维护不当则可能导致多线程状态下的不安全(一般不会,至少需要注意)。
对于具体的使用,需要注意其实现方法,下面是基于java8的实现方式:
/**
* 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() {
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();
}
/**
* 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) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* 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> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal的应用场景非常的多:
1)、之前项目中对于数据库链接池的管理
2)、在web项目中,会遇到很多情况下将数据存储到用户的session域中,但是则后面每个地方需要获取数据的时候,需要将request对象在controller层传入service层,可能会传入util方法中,或者传入其他相关处理的类中。然而这都是服务器的用户线程中,所以可以将需要的数据放入ThreadLocal中,在需要的地方之间进行获取即可。
3)、很多时候在Spring的Controller中使用非线程安全的容器或工具(经常会使用SimpleDateFormat,但这个还可以使用jota的DateTimeFormatter,可参见安全的SimpleDateForma)时,由于spring默认是单例,所以多线程的情况下线程非安全或者会照成错误,所以也可以使用ThreadLocal。
ThreadLocal的使用非常的方便,但是需要使用显示的将对象置为null,让其被gc,或者需要让gc不可达吗?至少在1.7,1.8中,由于ThreadLocal中的内置对象的ThreadLocalMap的Entry使用WeakReference(弱引用)方式,不需要显示的进行释放。如下:
package com.kevin.demo.concurrent;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalDemo {
public static final int CONCURRENT = 1000;
static volatile ThreadLocal threadLocal = new ThreadLocal(){
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println(this.toString() + "is gc !");
}
};
static volatile CountDownLatch latch = new CountDownLatch(CONCURRENT);
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
if (threadLocal.get() == null) {
threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println(this.toString() + " is gc !");
}
});
System.out.println(Thread.currentThread().getId() + " created SimpleDateFormat ! ");
}
Date parseDate = threadLocal.get().parse("2018-07-25 23:00:00");
} catch (ParseException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException {
final ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executorService.execute(new ParseDate(i));
}
latch.await();
System.out.println(" mission complete !");
threadLocal = null;
System.gc();
System.out.println(" first gc is complete ! ");
threadLocal = new ThreadLocal();
latch = new CountDownLatch(CONCURRENT);
for (int i = 0; i < 1000; i++) {
executorService.execute(new ParseDate(i));
}
latch.await();
Thread.sleep(1000);
System.gc();
System.out.println(" second gc is complete ! ");
}
}
我使用的jdk 1.8,执行结果如下:
F:\Java_jdk\jdk_8\jdk1.8.0_45\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:55622,suspend=y,server=n -javaagent:C:\Users\Administrator\.IntelliJIdea2018.1\system\captureAgent\debugger-agent.jar=file:/C:/Users/Administrator/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath "F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\charsets.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\deploy.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\access-bridge-32.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\dnsns.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\jaccess.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\localedata.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\nashorn.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\sunec.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\ext\zipfs.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\javaws.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\jce.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\jfr.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\jfxswt.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\jsse.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\management-agent.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\plugin.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\resources.jar;F:\Java_jdk\jdk_8\jdk1.8.0_45\jre\lib\rt.jar;G:\idel_workspace\kevin-demo\out\production\kevin-demo;C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.5\lib\idea_rt.jar" com.kevin.demo.concurrent.ThreadLocalDemo
Connected to the target VM, address: '127.0.0.1:55622', transport: 'socket'
18created SimpleDateFormat !
17created SimpleDateFormat !
19created SimpleDateFormat !
14created SimpleDateFormat !
20created SimpleDateFormat !
15created SimpleDateFormat !
16created SimpleDateFormat !
11created SimpleDateFormat !
13created SimpleDateFormat !
12created SimpleDateFormat !
mission complete !
com.kevin.demo.concurrent.ThreadLocalDemo$1@585f8ais gc !
first gc is complete !
17created SimpleDateFormat !
second gc is complete !
Disconnected from the target VM, address: '127.0.0.1:55622', transport: 'socket'
Process finished with exit code -1