之前有在项目中应用过ThreadLocal的示例,不过用的时候,也只是大致了解了ThreadLocal的应用场景,对于它的实现原理,并没有去深入看过。正巧在办公桌上的《java高并发程序设计》中看到了,遂了解一番,记录一波。
ThreadLocal是线程的局部变量,线程间无法读取彼此的数据,只能在当前线程访问到数据,是线程安全的。常见的应用场景:管理数据库的Connection。
书中的示例代码如下:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Description: <br>
*
* @author: name:yuxin
* Create Time: 2018/5/28 0028-下午 10:10<br>
*/
public class TlTest {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static class ParseDate implements Runnable{
int i = 0;
public ParseDate(int i){
this.i=i;
}
public void run(){
Date t = null;
try {
t = sdf.parse("2018-05-28 22:12:"+i%60);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(i+":"+t);
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
es.execute(new ParseDate(i));
}
}
}
由于SimpleDateFormat不是线程安全的,所以在代码运行中报错如下:
Exception in thread "pool-1-thread-18" java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:601)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at TlTest$ParseDate.run(TlTest.java:26)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
使用ThreadLocal解决上述问题
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Description: <br>
*
* @author: name:yuxin
* Create Time: 2018/5/28 0028-下午 10:10<br>
*/
public class TlTest {
private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
public static class ParseDate implements Runnable{
int i = 0;
public ParseDate(int i){
this.i=i;
}
public void run(){
Date t = null;
if(threadLocal.get()==null){
threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
SimpleDateFormat sdf = threadLocal.get();
try {
t = sdf.parse("2018-05-28 22:12:"+i%60);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(i+":"+t);
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
es.execute(new ParseDate(i));
}
}
}
ThreadLocal的实现原理
在上述的应用中,可以了解到ThreadLocal有set(),get()两个方法
set的实现如下
public void set(T value) {
//1.获取到当前线程对象
Thread t = Thread.currentThread();
//2.通过当前线程对象获取到ThreadLocalMap
ThreadLocalMap map = getMap(t);
//3.若获取到的ThreadLocalMap不为空,则以当前ThreadLocal对象作为key,将值存储
if (map != null)
map.set(this, value);
else
//4.若获取到的ThreadLocalMap为空,则创建ThreadLocalMap对象
createMap(t, value);
}
get的实现如下
public T get() {
//1.获取到当前线程对象
Thread t = Thread.currentThread();
//2.通过当前线程对象获取到ThreadLocalMap
ThreadLocalMap map = getMap(t);
//3.若获取到的ThreadLocalMap不为空,则以当前ThreadLocal对象作为key,将值取出,并返回值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//4.若获取到的ThreadLocalMap为空,则进行初始化,并将初始化的值返回
return setInitialValue();
}
通过以上代码,我们可以了解到ThreadLocal的值是存在于Thread的内部类ThreadLocalMap中的,在ThreadLocalMap中,key为ThreadLocal当前对象的弱引用。
当线程执行完毕,退出时,为了ThreadLocalMap能尽快被清理,会执行如下操作
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
//加速ThreadLocalMap被清理
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
如上,是在线程结束时,会执行的操作。但是,如果在一个应用了线程池的操作中使用了ThreadLocal,会隐藏着内存泄漏的风险。因为当线程结束后,线程并没有被exit,而是放回了线程池中,以后若不再使用ThreadLocalMap中的值,由于线程一直存在,ThreadLocalMap是无法被回收的。因此在该场景中使用ThreadLocal时,应注意在使用后,及时调用ThreadLocal.remove()方法,该方法代码如下:
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap中的remove方法代码如下:
/**
* Remove the entry for 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;
}
}
}