多线程访问共享可变数据时,涉及到线程间数据同步问题。并不是所有数据都用到共享数据,所以线程封闭概念就提出来了。
数据都被封闭在各自的线程中,就不需要同步,这种通过将数据封闭在线程中从而避免使用同步的技术称为线程封闭。
线程封闭涉及的技术有:ThreadLocal、局部变量。
ThreadLocal是一种特殊的变量,他是一个线程级别变量,每个线程有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。
ThreadLocal 使用场景有Session 管理、数据库连接管理等。
用法:
ThreadLocal<T> var = new Thread<T>();
会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。
可以用ThreadLocal存储一些参数,以便在线程中多个方法使用,用来替代方法传参的做法。
public class Demo {
/** threadLocal变量,每个线程都有一个副本,互不干扰 */
public static ThreadLocal<String> value = new ThreadLocal<>();
/**
* threadlocal Test
* @throws Exception
*/
public void threadLocalTest() throws Exception {
// 主线程设置值
value.set("这是主线程设置的123");
String v = value.get();
System.out.println("线程1执行之前,主线程取到的值:" + v);
new Thread(new Runnable() {
@Override
public void run() {
String v = value.get();
System.out.println("线程1取到的值:" + v);
// 设置 threadLocal
value.set("这是线程1设置的456");
v = value.get();
System.out.println("重新设置之后,线程1取到的值:" + v);
System.out.println("线程1执行结束");
}
}).start();
// 等待所有线程执行结束
Thread.sleep(5000L);
v = value.get();
System.out.println("线程1执行之后,主线程取到的值:" + v);
}
public static void main(String[] args) throws Exception {
new Demo().threadLocalTest();
}
}
执行结果:
线程1执行之前,主线程取到的值:这是主线程设置的123
线程1取到的值:null
重新设置之后,线程1取到的值:这是线程1设置的456
线程1执行结束
线程1执行之后,主线程取到的值:这是主线程设置的123
结果分析
ThreadLocal 是每一个线程的独立变量,主线程在一开始设置value的值为123, 子线程在执行的时候取到的值为null,因为主线程和子线程取到的是不同的变量副本,之后子线程设置value的值为456,主线程重新获取到的值还是自己线程的独立变量value 值为123,当线程销毁时,线程对应的ThreadLocal变量也销毁了。
源码解析
方法及属性概览
ThreadLocal内部是ThreadLocalMap静态内部类,其内部存储数据的成员变量Entry 是弱引用,换句话说在每一次GC的时候就会被回收。
在set(T) 方法中源码实现很简单,先获取对应线程的ThreadLocalMap, 如果为null,则重新new 一个ThreadLocalMap对象,并将当前线程作为key,变量作为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);
}
get() 方法的实现也是通过当前线程作为key 查找对应的ThreadLocalMap,存在则直接返回,如果不存在,则通过setInnitialValue()方法初始化一个value 为null 的ThreadLocalMap对象。
/**
* 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();
}
setInitialValue()方法实现如下:
/**
* 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;
}
由于前面提到,ThreadLocalMap 的底层数据是弱引用,每次GC都会被回收,如果Thread是从线程池中取出,它可能会被复用,此时就一定要保证这个Thread在上一次结束的时候,其关联的ThreadLocal被清空掉,否则就会串到下一次使用,将ThreadLocal变量定义成private static的,在调用ThreadLocal的get()、set()方法完成后,建议再调用remove()方法,手动删除不再需要的ThreadLocal,防止value值是强引用对象GC时不能被回收,造成内存泄漏。
知识扩展
栈封闭
局部变量的固有属性之一就是封闭在线程中,其他线程无法访问这个栈,栈封闭也称为线程内部使用或者线程局部使用。简单的说就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。