前面我们学习了同步控制线程安全,同步的实现有本地锁Synchronized关键字以及重入锁ReentrantLock。同步就是控制访问资源的角度实现线程安全,但是实现线程安全,不一定非得同步,也可以从增加共享资源的角度出发。
如果一个变量要被线程独享,可以通过java.lang.ThreadLocal类实现线程本地存储的功能。既然是本地存储的,那么就只有当前线程可以访问,自然是线程安全的。
package xidian.lili.ThreadLocal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLockDemo {
private static ThreadLocal t1=new ThreadLocal();
public static class ParseDate implements Runnable
{
int i=0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
if(t1.get()==null){
t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
try {
Date t=t1.get().parse("2018-07-29 19:29:" + i%60);
System.out.println(Thread.currentThread().getId()+":"+t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es=Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++){
es.execute(new ParseDate(i));
}
}
}
通过上面的例子我们知道,ThreadLocal支持泛型。是通过set方法和get方法来为线程创建局部变量,通过get方法检查当前线程是否持有SimpleDateFormat对象,不持有就新建一个然后存储到当前线程。这样就不存在资源共享,也就是线程安全的。
首先我们分析ThreadLocal的set和get方法源码实现。
get方法首先获取当前线程,然后获得线程的ThreadLocalMap,那说明每个线程内部都维护一个LocalThreadMap,确实我们在Thread源码中发现:
然后我们看ThreadLocal的方法getMap
所以我们就知道线程的局部变量是存在线程的threadLocals变量中,那么获取线程的threadLocals如果不为空,以当前ThreadLocal对象为key获得Entry,然后获取value
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();
}
set方法,首先获取当前线程,然后获取ThreadLocalMap,然后通过ThreadLocal对象为key,传入的对象为value,存入到map中。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
如上我们发现,通过ThreadLocal创建的线程局部变量都是存放在LocalThreadMap中,那么我们在来看一看LocalThreadMap类
它是ThreadLocal的静态内部类,java.lang.ThreadLocal.ThreadLocalMap
所以在当我们使用线程池的时候,当前线程未必会退出,那么在线程中的threadLocals变量(LocalThreadMap的实例)中的这些局部变量就会发生内存泄露,所以当你使用完ThreadLocal时,可以调用ThreadLocal.remove()来删除一个LocalThread对象,防止内存泄漏。
这是ThreadLocal的remove方法,它又会调用LocalThreadMap中的remove方法来清空threadLocals变量中的entry
还有一个方法就是,你让ThreadLocal对象为空,那么这个LocalThread对应的所有线程的局部变量也会被回收,因为LocalThreadMap的Entry继承弱引用,那么jvm可能就会把这个ThreadLocal所在的entry回收。