- 引入
先来看下面一段代码
package pack01;
import java.util.HashMap;
import java.util.Map;
class MyThread implements Runnable{
private Map<Thread,String> map;
public MyThread(Map<Thread, String> map) {
this.map = map;
}
@Override
public void run() {
String string = map.get(Thread.currentThread());
System.out.println("string:"+string);
}
}
public class Test {
public static void main(String[] args) {
Map<Thread,String> map = new HashMap<>();
map.put(Thread.currentThread(),"我是main线程");
Thread t = new Thread(new MyThread(map));
t.start();
}
}
当程序运行的时候,main线程向map中存入了数据"我是main线程",当想要通过t线程去读取map中的数据时,发现结果是string:null;
这是因为map的键是当前线程对象,所以只有通过本线程才能找到对应的值,这一点和ThreadLocal的原理很像.
- 概述
java.lang.ThreadLocal;
public class ThreadLocal<T>extends Object
ThreadLocal不是一个Thread,更确切的说,它是一个线程局部变量,它为每一个使用该变量的线程提供一个独立于该变量的副本,每个线程可以独立的使用自己的副本,该副本与当前线程绑定.通过ThreadLocal操作的数据也都是与本线程相关的,多线程并发时,ThreadLocal为每一个线程都提供了一份变量,解决多线程资源共享的问题.
ThreadLocal有一个静态内部类static class ThreadLocalMap,它的键就是使用该变量的线程对象,值是Object类型的变量(本类的其它任何变量均可)
- 方法
构造方法: ThreadLocal() 创建一个线程本地变量
成员方法:
1. T get()
返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
2. void set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,它们只依靠initialValue()方法来设置线程局部变量的值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
3. void remove()
移除此线程局部变量当前线程的值。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
4. initialValue()
返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get()方法访问变量时将调用此方法,但如果线程之前调用了 set(T)方法,则不会对该线程再调用initialValue 方法。通常,此方法对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。
该实现返回 null;如果程序员希望线程局部变量具有 null 以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。
protected T initialValue() {
return null;
}
以上代码
部分来自JDK1.7源码
4.ThreadLocalMap
ThreadLocal中的静态内部类,也是ThreadLocal类的关键,里面存放了一个大小为16的数组,数组元素是Entry对象(静态内部类),
Entry的数据结构是key-value形式的,键就是使用该变量的线程对象
static class Entry extends WeakReference<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);
}
当使用ThreadLocal保存一个变量的时候,就会在ThreadLocalMap中插入一个Entry对象,因为Entry继承了WeakReference类,属于弱引用类型
如果创建ThreadLocal的线程一致运行,Entry中的key因为是弱引用类型可以被GC回收,value却得不到回收,会产生内存泄漏,此类问题在
ThreadLocalMap的get()和set()方法中,会判断如果key为null,就会清除对应的value
以上代码部分来自JDK1.7源码
5.应用举例
public class ConnectionManager {
//1: 创建一个ThreadLocal对象
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
public static Connection getConnection() throws SQLException{
//1:如果是第一次线程调用该方法,则存入一个Connection对象,并将对象返回
//先尝试获取一次,如果返回的是null,就表示第一次
Connection conn = tl.get();//哪个线程来调用该方法,这里的键就是该线程对象
if(conn == null){
conn = MyC3P0Utils.getConnection();
tl.set(conn);
}
return conn;
}
}
这是一个管理连接的工具类,当调用此类的getConnection()方法时,希望同一线程每次获取的连接总是同一个,就使用到ThreadLocal
参考文档:JDK官方文档