前边我们讲述多线程交互,多线程引起的安全问题,多线程安全的问题解决同步(synchronized、lock、CAS)……这一切的一切起源就是共享资源,共享临界区的数据安全引起的。那我们从另外一个角度想想呢?每个线程有自己的一份数据,是不是就会避免共享资源的数据问题了?ThreadLocal就是从这个角度出发而产生的,好,下边我们重点看看这个东东。
一,简单使用:ThreadLocal是线程的局部变量,也就是说每个线程有自己单独的一块存独享数据的空间。属于线程的私有财产。好看下简单使用:
public class ThreadLocalTest {
//1,线程不安全
//2,加锁控制
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//3使用ThreadLocal容器
public static ThreadLocal ts = new ThreadLocal();
public static class ParseDate implements Runnable{
int i=0;
public ParseDate(int i ){
this.i=i;
}
@Override
public void run() {
try {
//线程不安全
//Date t = sdf.parse("2018-12-09 12:29:" + i%60);
//通过threadocal人手一个SimpleDateFormat
if(ts.get()==null){
ts.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
Date t = ts.get().parse("2018-12-09 12:29:" + i%60);
System.out.println(i + ":" + t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
es.execute(new ParseDate(i));
}
}
}
二,实现原理(源码分析):
1,set方法:
/**
* 一,set方法
* 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();
//1,获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//2,为当前线程创建一个ThreadLocalMap
createMap(t, value);
}
/**
* 1,getMap(t)
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 2,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);
}
/**
* ThreadLocalMap为ThreadLocal的一个内部类
* 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 {}
2,get方法:这里注意一下其中的弱应用,方便垃圾回收,看一下弱引用的文章:https://www.cnblogs.com/absfree/p/5555687.html,https://blog.csdn.net/zmx729618/article/details/54093532
/**
* 二,get方法
* 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) {
//1,Entry为ThreadLocalMap内部类,弱应用
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//2,如果不存在,则进行初始化
return setInitialValue();
}
/**
* 1,Entry
* 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;
}
}
/**
* 2,初始化map
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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;
}
/**
* JDK默认的初始化值为null,当然如果我们想修改,则继承进行method overridden即可
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the {@code initialValue} method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
*
* This implementation simply returns {@code null}; if the
* programmer desires thread-local variables to have an initial
* value other than {@code null}, {@code ThreadLocal} 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;
}
3,何时GC这些线程私有对象:
/**
* 一,线程退出时
* 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;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
可以看出当exit()时,Thread才会进行各种(包括ThreadLocalMap)清理工作。因此如果我们使用Thread Pool,Thread未必exit(),所以ThreadLocalMap就一直存在,而在使用中如果我们将一下大的对象set进入,就非常容易造成内存泄漏的情况。这就需要我们手动进行清除了,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);
}
三,使用性能,其实很明显的使用自己私有的变量可能比使用公共的变量性能会好很多,这里引入一个Future的例子,了解一下,重点看下,使用ThreadLocal和不适用的性能差距……
public class ThreadLocalHSTest {
public static final int Get_COUNT = 1000000;
public static final int THREAD_COUNT = 4;
static ExecutorService es = Executors.newFixedThreadPool(THREAD_COUNT);
public static Random rd =new Random(123);
public static ThreadLocal trd = new ThreadLocal(){
@Override
protected Random initialValue() {
return new Random(123);
}
};
public static class RndTask implements Callable{
private int mode =0;
public RndTask(int mode){
this.mode = mode;
}
public Random getRandom(){
if(mode==0){
return rd;
}else if(mode ==1 ){
return trd.get();
}else{
return null;
}
}
@Override
public Long call() throws Exception {
long b = System.currentTimeMillis();
for (int i = 0; i < Get_COUNT; i++) {
getRandom().nextInt();
}
long e= System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "spend" + (e-b) + "ms");
return e-b;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Future[] future = new Future[Get_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
future[i] = es.submit(new RndTask(0));
}
long totalTime = 0;
for (int i = 0; i < THREAD_COUNT; i++) {
totalTime +=future[i].get();
}
System.out.println("多线程访问同一个Random实例:" + totalTime + "ms");
for (int i = 0; i < THREAD_COUNT; i++) {
future[i] = es.submit(new RndTask(1));
}
totalTime = 0;
for (int i = 0; i < THREAD_COUNT; i++) {
totalTime +=future[i].get();
}
System.out.println("使用ThreadLocal包装Random实例:" + totalTime + "ms");
}
}
pool-1-thread-2spend311ms
pool-1-thread-4spend330ms
pool-1-thread-1spend331ms
pool-1-thread-3spend331ms
多线程访问同一个Random实例:1303ms
pool-1-thread-3spend28ms
pool-1-thread-2spend31ms
pool-1-thread-1spend30ms
pool-1-thread-4spend31ms
使用ThreadLocal包装Random实例:120ms
ThreadLocal,对于线程保存自己的变量,在其生命周期中进行使用是非常方便,但是要注意线程池中的使用,以及ThreadLocalMap的垃圾回收,防止出现内存溢出……