通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每个线程都有专属的本地变量,那该如何实现? JDK 中提供的 ThreadLocal
类正是为了解决这样的问题,ThreadLocal
类主要解决的,就是让每个线程绑定自己的值,可以将 ThreadLocal
类形象的比喻成存放数据的盒子,这个盒子中可以存储每个线程的私有数据。
一旦创建了 ThreadLocal
变量,那么访问这个变量的每个线程,都会持有这个变量的本地副本,这也是 ThreadLocal
变量名的由来。通过使用 get()
和 set()
方法来获取默认值,将其值更改为当前线程所存的副本的值,从而避免了线程安全的问题。
举个简单的生活化例子:有两个人去宝屋收集宝物,两个人共用一个袋子的话,肯定会产生争执,但是给他们每个人分配一个袋子的话,就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。
import java.text.SimpleDateFormat;
import java.util.Random;
public class ThreadLocalExample implements Runnable {
// SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
public static void main(String[] args) throws InterruptedException {
ThreadLocalExample obj = new ThreadLocalExample();
for(int i=0 ; i<10; i++){
Thread t = new Thread(obj, ""+i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
@Override
public void run() {
System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//formatter pattern is changed here by thread, but it won't reflect to other threads
formatter.set(new SimpleDateFormat());
System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
}
}
Output:
Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm
从上述打印可以看到,虽然 Thread-0
已经改变了 formatter 的值,但 Thread-2
默认的格式化规则与初始化值相同,其他线程也一样。