什么是ThreadLocal,它能做什么
ThreadLocal是Jdk1.2开始提供的一种线程封闭的技术,这个类全包路径 java.lang.ThreadLocal。他使得每一个线程都能拥有ThreadLocal保护的变量的独立副本,在线程内部使用变量时,就如同在方法内部使用局部变量一样不比担心多线程环境下的并发安全性。它常用于防止可变的单实例变量或者全局变量进行共享。
为什么用它
我目前知道的避免并发访问问题有大概这么几种方式:锁(syncronized、Lock)、线程封闭、栈封闭(方法局部变量)。
使用锁来控制全局变量的访问显然存在效率问题;使用栈封闭需要考虑溢出问题(可以类比安全发布的概念),同时变量会在方法内部创建,如果线程执行的多个方法需要访问这个变量,就不好办了。于是,线程封闭可以缓解上面两种技术带来的问题,它使得每个线程都能获取到最原始的被其保护后的变量的值,每个线程对这个全局ThreadLocal变量的修改不会影响到其它变量;并且,这个ThreadLocal变量对线程内执行的所有方法可见,线程执行的方法对其修改只在线程内部有效。
怎么用它
先贴一段代码
public class Portal
{
class InnerTest
{
private int i;
InnerTest()
{
i = 0;
}
public int getI()
{
return i;
}
public void setI(int i)
{
this.i = i;
}
}
private ThreadLocal<InnerTest> test1 = new ThreadLocal<InnerTest>()
{
@Override
protected InnerTest initialValue()
{
return new InnerTest();
};
};
private ThreadLocal<InnerTest> test2 = new ThreadLocal<InnerTest>();
private ThreadLocal<InnerTest> test = null;
private volatile boolean f1 = false;
private volatile boolean f2 = false;
void main1()
{
Thread thread1 = new Thread(new Runnable()
{
@Override
public void run()
{
System.out.println("thread1执行");
if (test == null)
{
throw new RuntimeException();
}
InnerTest t = test.get();
System.out.println("is t null? " + (t == null));
t.setI(1);
f1 = true;
while (true)
{
if (f2)
{
System.out.println("thread1 value:" + t.getI());
break;
}
}
}
});
Thread thread2 = new Thread(new Runnable()
{
@Override
public void run()
{
while (true)
{
if (f1)
{
System.out.println("thread2执行");
if (test == null)
{
throw new RuntimeException();
}
InnerTest t = test.get();
System.out.println("is t null? " + (t == null));
t.setI(2);
f2 = true;
break;
}
}
}
});
thread1.start();
thread2.start();
}
void main2()
{
Thread thread1 = new Thread(new Runnable()
{
@Override
public void run()
{
System.out.println("thread1执行");
if (test == null)
{
throw new RuntimeException();
}
InnerTest t = test.get();
System.out.println("is t null? " + (t == null));
if (t == null)
{
test.set(new InnerTest());
t = test.get();
}
t.setI(1);
f1 = true;
while (true)
{
if (f2)
{
System.out.println("thread1 value:" + t.getI());
break;
}
}
}
});
Thread thread2 = new Thread(new Runnable()
{
@Override
public void run()
{
while (true)
{
if (f1)
{
System.out.println("thread2执行");
if (test == null)
{
throw new RuntimeException();
}
InnerTest t = test.get();
System.out.println("is t null? " + (t == null));
t.setI(2);// 此处抛出空指针异常,thread1的赋值操作只在线程内部有效
f2 = true;
break;
}
}
}
});
thread1.start();
thread2.start();
}
public static void main(String[] args)
{
Portal portal = new Portal();
portal.test = portal.test1;
portal.main1();
// portal.test = portal.test2;
// portal.main2();
}
}
执行结果
thread1执行
is t null? false
thread2执行
is t null? false
thread1 value:1
以上代码演示了ThreadLocal基本使用方法,从里面可以得到这些信息:
- 如何创建ThreadLocal变量
- 如何访问ThreadLocal变量
通过get方法 - ThreadLocal变量两种初始化方法
· 创建ThreadLocal变量时重写其initialValue方法
· 直接new出ThreadLocal变量,在线程内部首次使用时,首先判断是否为空,为空则主动设置其值 - 两种方式验证,在子线程内部修改主线程全局ThreadLocal变量内的值,修改只对本线程内部有效(不影响其他线程读取到最原始的值)
注意事项
- ThreadLocal内部使用ThreadLocalMap来保存数据,ThreadLocalMap并不是Map的实现,但是内部存贮数据的也是一个数组(Object数组)。
- ThreadLocal需要指定泛型类型,否则默认为Object,因此其只可保护引用类型数据。
- 有兴趣的可以关注它的两个子类java.lang.InheritableThreadLocal和org.springframework.core.NamedThreadLocal