1. ThreadLocal是什么?
ThreadLocal适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用(相同线程数据共享),也就是变量在线程间隔(不同的线程数据隔离)而在方法或类间共享的场景
2. 使用举例
不适用threadlocal
public class MyThreadLocalDemo { private String str; private String getString() { return str; } private void setString(String str) { this.str = str; } public static void main(String[] args) { int threads = 10; MyThreadLocalDemo demo = new MyThreadLocalDemo(); CountDownLatch countDownLatch = new CountDownLatch(threads); for (int i = 0; i < threads; i++) { Thread thread = new Thread(() -> { demo.setString(Thread.currentThread().getName()); System.out.println(demo.getString()); countDownLatch.countDown(); }, "thread - " + i); thread.start(); } } }
thread - 1
thread - 1
thread - 4
thread - 5
thread - 8
thread - 2
thread - 3
thread - 6
thread - 7
thread - 9
从结果中看出多个线程访问同一个变量的时候出现异常,线程间的数据没有隔离
使用threadlocal
public class MyThreadLocalDemo1 { private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); private String getString() { return threadLocal.get(); } private void setString(String string) { threadLocal.set(string); } public static void main(String[] args) { int threads = 10; CountDownLatch downLatch = new CountDownLatch(threads); MyThreadLocalDemo1 demo1 = new MyThreadLocalDemo1(); for (int i = 0; i < threads; i++) { Thread thread = new Thread(() -> { demo1.setString(Thread.currentThread().getName()); System.out.println(demo1.getString()); downLatch.countDown(); }, "thread - " + i); thread.start(); threadLocal.remove(); } } } thread - 0 thread - 1 thread - 2 thread - 6 thread - 5 thread - 3 thread - 7 thread - 8 thread - 4 thread - 9
相同线程数据共享,不同线程数据隔离
对象实例与 ThreadLocal
变量的映射关系是存放的一个 Map
里面(这个 Map
是个抽象的 Map
并不是 java.util
中的 Map
),而这个 Map
是 Thread
类的一个字段!而真正存放映射关系的 Map
就是 ThreadLocalMap
1)实际通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量;
3)在进行get之前,必须先set,否则会报空指针异常;因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。