1、什么是ThreadLocal
ThreadLocal:是 Thread 的局部变量(不是线程,更不是本地线程的意思) ,是每个线程独享的本地变量,每个线程都有自己的 ThreadLocal,它们是线程隔离的。
例子:测试类 ThreadLocalTest2
package com.looper.day1.thread;
public class ThreadLocalTest2 {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName+" : "+threadLocal.get());
threadLocal.set(0);
System.out.println(threadName+" : "+threadLocal.get());
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName+" : "+threadLocal.get());
threadLocal.set(1);
System.out.println(threadName+" : "+threadLocal.get());
}
});
thread1.start();
thread2.start();
}
}
结果:
Thread-0 : null
Thread-0 : 0
Thread-1 : null
Thread-1 : 1
结论:说明 ThreadLocal 是线程隔离的,每个线程中得到的都是初始值,ThreadLocal 对象不会因为某一个线程改变值,而导致其他线程获取的值发生变化。
2、ThreadLocal的作用
ThreadLocal:用来解决多线程程序的并发问题。
当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
作用:为每个线程对象创建自己独立的的副本。
3、ThreadLocal类中的方法
void set(T value)
:
将此线程局部变量的当前线程副本中的值设置为指定值;
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
T get()
:
返回此线程局部变量的当前线程副本中的值。
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();
}
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;
}
void remove()
:
移除此线程局部变量当前线程的值;
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
protected T initialValue()
:
返回此线程局部变量的当前线程的初始值;
protected T initialValue() {
return null;
}
4、ThreadLocal的存储原理
ThreadLocal 是解决线程安全问题一个很好的思路,ThreadLocal 本身不存储任何值,ThreadLocal 类中有一个 Map,用于存储每一个线程的变量副本,Map 中元素的键为线程对象,而值对应线程的变量副本,由于 Key 值不可重复,每一个「线程对象」对应线程的「变量副本」,而到达了线程安全。
5、ThreadLocal使用场景
- 场景1:每个线程需要一个独享的对象,通常是工具类,比如典型的SimpleDateFormat和Random等。
public class ThreadLocalTest1 {
public static ExecutorService THREAD_POOL = Executors.newFixedThreadPool(10);
static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
THREAD_POOL.submit(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalTest1().date(finalI);
System.out.println(date);
}
});
}
// 关闭线程池,此种关闭方式不再接受新的任务提交,等待现有队列中的任务全部执行完毕之后关闭
THREAD_POOL.shutdown();
}
private String date(int seconds) {
// 参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
Date date = new Date(1000 * seconds);
String format;
synchronized (ThreadLocalTest1.class){
format = DATE_FORMAT.format(date);
}
return format;
}
}
- 场景2:每个线程内需要保存线程内的全局变量,这样线程在执行多个方法的时候,可以在多个方法中获取这个线程内的全局变量,避免了过度参数传递的问题。
比如网站的某个用户登录之后,每一个业务处理的时候都需要判断当前用户是不是会员,这个用户信息在登录之后,就希望他只一个全局的,可共享的。
单线程情况下,该用户信息可以为设为 static 没问题,进行串行处理;但是多线程或者并发情况下就会有问题,对这个前一个线程对用户信息 set 之后,下一个线程 get 到的用户信息,就不一定值最初的了。
6、总结
ThreadLocal 使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal 为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但是大大减少了线程同步所带来性能消耗(空间换时间)(Synchronized 属于时间换空间),也减少了线程并发控制的复杂度。