作用:
使线程拥有自己的实例副本,且该副本只允许当前线程使用。
使用场景:
1、在线程生命周期内传值
2、ThreadLocal解决多线程的并发问题,是Thread的局部变量,使用它维护变量,会使该变量的线程提供一个独立的副本,可以独立修改,不会影响其他线程的副本
简单使用:
package com.androidreviewdemo;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
ThreadLocal longLocal = new ThreadLocal() {
@Override
protected Long initialValue() {
return Thread.currentThread().getId();
}
};
ThreadLocal stringLocal = new ThreadLocal() {
@Override
protected String initialValue() {
return Thread.currentThread().getName();
}
};
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final ThreadId test = new ThreadId();
//test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread() {
public void run() {
//test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
}
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
说明:ThreadLocal 通过set方法进行赋值,通过get方法获取值。如果在创建时复写了initialValue方法并赋值后,则可以直接调用get方法获取值,否则需先set值然后再获取值,不然获取的值为null。
线程安全实例:
假设我们在一个线程应用中需要对时间做格式化,我们很容易想到的是使用SimpleDateFormat这个工具类,但是SimpleDateFormat不是线程安全的,那么我们通常用两种做法:
1 每次用到的时候new一个SimpleDateFormat对象,使用完丢弃,交给gc
2 每个线程维护一个SimpleDateFormat实例,线程运行期间不重复创建
那么无论从执行效率还是内存占用方面,我们都倾向于使用后者,即线程私有一个SimpleDateFormat对象,这时候,ThreadLocal就是很好的应用,示例代码如下:
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestTask implements Runnable {
private boolean stop = false;
private ThreadLocal sdfHolder = new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
@Override
public void run() {
while (!stop) {
String formatedDateStr = sdfHolder.get().format(new Date());
System.out.println("formated date str:" + formatedDateStr);
//may be sleep for a while to avoid high cpu cost
}
sdfHolder.remove();
}
//something else
}
源码分析:
1.ThreadLocal 是一个泛型类且有一个构造函数
public class ThreadLocal {
public ThreadLocal() {
}
}
2.ThreadLocal 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();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//this为创建的ThreadLocal 对象
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
说明:我们从源码可以看出,ThreadLocal内部维护着一个map,但是并不是使用的是hashmap,而是实现的ThreadLocalMap内部类。首先我们获取到当前线程的实例,每个Thread里面都有一个threadLocals(ThreadLocalMap),我们通过getMap()方法获取ThreadLocalMap,如果map不为null则赋值,为null则重新创建当前线程的ThreadLocalMap并赋值。所以每一个线程的赋值取值都不受其他线程的影响。
3.ThreadLocal get方法解析
public T get() {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
if (map != null) {//不为null
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//如果map为null,则调用初始化值方法
}
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;
}
protected T initialValue() {//默认为null
return null;
}
内存泄漏
我们从ThreadLocalMap的源码可知,ThreadLocalMap弱引用ThreadLocal,当ThreadLocal没有被ThreadLocalMap以外的对象进行引用时,产生GC时,ThreadLocal将会被清除,那么ThreadLocalMap 的K将为null,此时强引用V一般不会被访问到。所以,只要Thread实例一直存在,Thread实例就强引用着ThreadLocalMap,因此ThreadLocalMap就不会被回收,那么这里K为null的V就一直占用着内存。
static class ThreadLocalMap {
/**
* 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;
}
}
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);
}
}
ThreadLocalMap 本身的优化
我们从ThreadLocalMap中的set方法可知,当遇到key为null的情况时,会重复利用空间。remove和get方法则会清除
private void set(ThreadLocal key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {//替换map中key为null的entry,实现空间再利用
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}