文章目录
(一)基础认识
(1)概述
定义:提供线程局部变量,一个线程局部变量在多个线程中,分别有独立的值(副本)
特点:简单(开箱即用)、快速(无额外开销)、安全(线程安全)
场景:多线程场景(资源持有、线程一致性、并发计算、线程安全等)
实现原理:Java中用哈希表实现
应用范围:几乎所有提供多线程特征的语言
(2)ThreadLocal API
public class ThreadLocal<T>extends Object
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
T get()
返回此线程局部变量的当前线程副本中的值。
protected T initialValue()
返回此线程局部变量的当前线程的“初始值”。
void remove()
移除此线程局部变量当前线程的值。
void set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。
(3)基本实例
public class Demo1 {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
System.out.println("initialValue...");
return Thread.currentThread().getId();
}
};
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
System.out.println("线程二:" + Thread.currentThread().getId());
System.out.println("线程二:" + threadLocal.get());
threadLocal.set(100L);
System.out.println("线程二:" + threadLocal.get());
}
}.start();
System.out.println("主线程:" + threadLocal.get());
}
}
案例代码结论:
1:每个线程都拥有独立的ThreadLocal对象
2:initialValue()方法是延迟加载的,当调用get方法时才调用,如果在get前有set方法的话不会执行
3:某个线程对于ThreadLocal的修改不会影响到其他线程的ThreadLocal数据
(二)ThreadLocal的使用——线程运行耗时统计
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这 个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个 线程上的一个值。 可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
在下面的例子中,构建了一个常用的Profiler类,它具有begin()和end()两个 方法,而end()方法返回从begin()方法调用开始到end()方法被调用时的时间差,单位是毫秒。
import java.util.concurrent.TimeUnit;
public class Profiler {
/**
* 第一次get()方法调用时会进行初始化(如果set方法没有调用),每个线程会调用一次
*/
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return System.currentTimeMillis();
} };
public static final void begin() {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
public static final long end() {
return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws Exception {
Profiler.begin(); TimeUnit.SECONDS.sleep(1);
System.out.println("Cost: " + Profiler.end() + " mills");
}
}
Profiler可以被复用在方法调用耗时统计的功能上,在方法的入口前执行begin()方法,在方法调用后执行end()方法,好处是两个方法的调用不用在一个方法或者类中,比如在AOP(面 向方面编程)中,可以在方法调用前的切入点执行begin()方法,而在方法调用后的切入点执行 end()方法,这样依旧可以获得方法的执行耗时。
(三)使用场景分析
在通常的业务开发中,ThreadLocal 有两种典型的使用场景。
场景1,ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,每个线程都只能修改自己所拥有的副本, 而不会影响其他线程的副本,这样就让原本在并发情况下,线程不安全的情况变成了线程安全的情况。
场景2,ThreadLocal 用作每个线程内需要独立保存信息的场景,供其他方法更方便得获取该信息,每个线程获取到的信息都可能是不一样的,前面执行的方法设置了信息后,后续方法可以通过 ThreadLocal 直接获取到,避免了传参。