您的位置:首页 > 其它

深入理解ThreadLocal

2016-11-11 20:16 567 查看

1、ThreadLocal是什么

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程对应的副本

2、ThreadLocal类提供的4个方法

  • public void set(T value)

        设置当前线程的线程局部变量的值

/**
* 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);
else
createMap(t, value);
}
  • public T 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) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
  • public void remove()

   将当前线程的局部变量的值删除    

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
  • protected T initialValue()

        返回该线程局部变量的初始值,该方法是一个protected方法,显然是为了让子类覆盖而设计的,ThreadLocal中的缺省实现直接返回一个null

protected T initialValue() {
return null;
}

3、每个线程的变量副本存储在哪里

ThreadLocal有一个静态内部类ThreadLocalMap,该内部类保存了当前线程所对应的变量的副本(需要注意的是,变量是保存在线程中的,而不是保存在ThreadLocal变量中)

Thread类表示的当前线程中,有一个变量引用名是threadLocals,这个引用是在ThreadLocal类中createMap函数内初始化的

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

我们所使用的ThreadLocal变量的实际数据,通过get函数实际取值的时候,就是通过取出Thread类中threadLocals引用的map,然后从这个map中根据当前threadLocal作为参数,取出数据

4、变量副本是怎么从共享的那个变量复制出来的,ThreadLocal的初始值什么时候设置的?

准确的说,应该是,变量副本【每个线程中保存的那个map中的变量】是怎么声明和初始化的

从set函数可以看出,当前线程的ThreadLocalMap是在第一次调用set的时候创建map并设置相应的值的

5、一个线程声明了n(n>=2)个这样的局部变量threadLocal,那么在Thread类中的threadLocals是怎么存储的?

在ThreadLocal的set函数中可以看到,其中的map.set(this, value)把当前的ThreadLocal传入到map中作为键,也就是说,在不同线程的threadLocals变量中,都会有一个以你所声明的那个线程局部变量threadLocal作为键的key-value。假设你声明了n个这样的线程局部变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对

6、应用场景

当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal

7、典型应用

ThreadLocal在Hibernate中的应用

private static final ThreadLocal threadSession = new ThreadLocal();

public static Session getSession() throws InfrastructureException {
//首先判断当前线程中有没有放进去session
Session s = (Session) threadSession.get();
try {
//如果还没有,通过sessionFactory.openSession()来创建一个session,再将session放到线程中
//实际上是放到当前线程的ThreadLocalMap中,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}

试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中

或者自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中

 8、代码示例

8.1 例1

public class ThreadLocalTest {

//创建一个Integer型的线程本地变量
public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};

public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//获取当前线程的本地变量,然后累加5次
int num = local.get();
for (int j = 0; j < 5; j++) {
num++;
}
//重新设置累加后的本地变量
local.set(num);
System.out.println(Thread.currentThread().getName() + ":" + local.get());
}
}, "Thread-" + i).start();
}
}

}

 

 运行后结果:

Thread-0:5
Thread-3:5
Thread-2:5
Thread-1:5
Thread-4:5

可以看到,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,县城之间互不影响。

8.2 例2

public class ThreadLocalTest {

private static Index num = new Index();

//创建一个Integer型的线程本地变量
public static final ThreadLocal<Index> local = new ThreadLocal<Index>() {
@Override
protected Index initialValue() {
return num;
}
};

public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//获取当前线程的本地变量,然后累加5次
Index index = local.get();
for (int j = 0; j < 5; j++) {
index.increase();
}
//重新设置累加后的本地变量
local.set(index);
System.out.println(Thread.currentThread().getName() + ":" + local.get().num);
}
}, "Thread-" + i).start();
}
}

static class Index {
int num;
public void increase() {
num++;
}
}

}

 

执行结果(每次运行结果都不一样):

Thread-0:5
Thread-1:20
Thread-3:25
Thread-4:15
Thread-2:10

原因:

ThreadLocal可以给一个初始值,而每个线程获得的是这个初始值的副本,而不是要创建对象引用的副本

public static final ThreadLocal<Index> local = new ThreadLocal<Index>() {
@Override
protected Index initialValue() {
return new Index(); //注意这里
}
};

 

改为以上方法后,正确输出结果:

Thread-0:5
Thread-2:5
Thread-4:5
Thread-3:5
Thread-1:5

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: