ThreadLocal含义
ThreadLocal叫做线程本地变量,也有叫线程局部变量。ThreadLocal提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是一致的,这就是所谓的线程隔离
应用场景
ThreadLocal通常用来共享数据,当需要在多个方法中使用某个变量,这个变量是当前线程的状态,其他线程不依赖这个变量,看下面例子
import java.util.HashMap;
import java.util.Map;
class MyThread implements Runnable {
private Map map = new HashMap(); // 定义HashMap
@Override
public void run() {
for (int i = 0; i < 10; i++) {
map.put(i, i); // 向map中添加值
try {
Thread.sleep(100);
} catch (Exception ex) {
}
}
System.out.println(Thread.currentThread().getName() + "# map.size()=" +
map.size() + " # " + map);
}
}
public class ThreadLocalTest {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread[] runs = new Thread[5]; // 创建5个线程
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(mt);
runs[i].start(); // 启动线程
}
}
}
程序输出结果(视情况而定):
Thread-4# map.size()=14 # {0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
Thread-1# map.size()=14 # {0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
Thread-3# map.size()=14 # {0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
Thread-0# map.size()=14 # {0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
Thread-2# map.size()=14 # {0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
启动5个线程,线程向map中写入10个整数值,然后输出map。运行该程序,观察结果我们会发现map中不止10个元素,这说明程序产生了线程安全问题
我们都知道HashMap是非线程安全的,程序启动了10个线程,他们共享了同一个map,10个线程都往map写对象,势必引起线程安全问题。
解决方法可以将map的声明放在run方法中,这样map就成了方法内部变量也可以将HashMap换成Hashtable。当然我们需要另外一种解决方案,使用ThreadLocal。
上面例子修改如下:
import java.util.HashMap;
import java.util.Map;
class GetLocal {
static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>() {
@Override
protected HashMap initialValue() {
return new HashMap();
}
};
}
class MyThread implements Runnable {
// private Map map = new HashMap();
@Override
public void run() {
Map map = GetLocal.threadLocal.get();
for (int i = 0; i < 10; i++) {
map.put(i, i); // 向map中添加值
try {
Thread.sleep(200);
} catch (Exception ex) {
}
}
System.out.println(Thread.currentThread().getName() + "# map.size()=" +
map.size() + " # " + map);
}
}
public class ThreadLocalTest {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread[] runs = new Thread[5]; // 创建5个线程
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(mt);
runs[i].start(); // 启动线程
}
}
}
实现原理
上面介绍了对ThreadLocal的简单使用,接下来看一下具体ThreadLocal是如何实现的。
先了解一下ThreadLocal类提供的几个方法:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法
get()方法源码
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
getMap方法源码
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
get()方法的大致意思就是从当前线程中拿到ThreadLocalMap的实例threadLocals,如果threadLocals不为空,那么就以当前ThreadLocal实例为KEY从threadLocals中拿到对应的VALUE。如果不为空,那么就调用setInitialValue()方法初始化threadLocals,最终返回的是initialValue()方法的返回值。
下面是setInitialValue()方法的源码
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;
}
我们看到map.set(this, value);这句代码将ThreadLocalMap的实例作为KEY,将initialValue()的返回值作为VALUE,set到了threadLocals中。
参考资料
http://ifeve.com/threadlocal%E4%BD%BF%E7%94%A8/
http://www.cnblogs.com/dolphin0520/p/3920407.html
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/14207.html