ThreadLocal入门
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。
ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
ThreadLocal 适用于变量在线程间隔离而在方法或类间共享的场景。
使用示例:
1 public class ThreadLocalDemo { 2 public static void main(String[] args) throws InterruptedException { 3 int threads = 3; 4 CountDownLatch countDownLatch = new CountDownLatch(threads); 5 InnerClass innerClass = new InnerClass(); 6 for(int i = 1; i <= threads; i++) { 7 new Thread(() -> { 8 for(int j = 0; j < 4; j++) { 9 innerClass.add(String.valueOf(j)); 10 innerClass.print(); 11 } 12 innerClass.set("hello world"); 13 countDownLatch.countDown(); 14 }, "thread - " + i).start(); 15 } 16 countDownLatch.await(); 17 } 18 private static class InnerClass { 19 public void add(String newStr) { 20 StringBuilder str = Counter.counter.get(); 21 Counter.counter.set(str.append(newStr)); 22 } 23 public void print() { 24 System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s ", 25 Thread.currentThread().getName(), 26 Counter.counter.hashCode(), 27 Counter.counter.get().hashCode(), 28 Counter.counter.get().toString()); 29 } 30 public void set(String words) { 31 Counter.counter.set(new StringBuilder(words)); 32 System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s ", 33 Thread.currentThread().getName(), 34 Counter.counter.hashCode(), 35 Counter.counter.get().hashCode(), 36 Counter.counter.get().toString()); 37 } 38 } 39 private static class Counter { 40 private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() { 41 @Override 42 protected StringBuilder initialValue() { 43 return new StringBuilder(); 44 } 45 }; 46 } 47 }
输出:
1 Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0 2 Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0 3 Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0 4 Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:01 5 Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:01 6 Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:012 7 Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0123 8 Set, Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1362597339, Value:hello world 9 Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:01 10 Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:012 11 Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:012 12 Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0123 13 Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0123 14 Set, Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:482932940, Value:hello world 15 Set, Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1691922941, Value:hello world
- 从第1-3行输出可见,每个线程通过 ThreadLocal 的 get() 方法拿到的是不同的 StringBuilder 实例
- 第1-3行输出表明,每个线程所访问到的是同一个 ThreadLocal 变量
- 对比第1行与第15行输出并结合第38行代码可知,使用 set(T t) 方法后,ThreadLocal 变量所指向的 StringBuilder 实例被替换
ThreadLocal原理
1 public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) { 5 ThreadLocalMap.Entry e = map.getEntry(this); 6 if (e != null) { 7 @SuppressWarnings("unchecked") 8 T result = (T)e.value; 9 return result; 10 } 11 } 12 return setInitialValue(); 13 }
2,3行取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。每一个线程都有一个ThreadLocalMap。
ThreadLocalMap的键是this,也就是调用get()的ThreadLocal对象,值是实例。所以不同线程拿到的实例也是不同的。
1 static class Entry extends WeakReference<ThreadLocal<?>> { 2 /** The value associated with this ThreadLocal. */ 3 Object value; 4 Entry(ThreadLocal<?> k, Object v) { 5 super(k); 6 value = v; 7 } 8 }
与 HashMap 不同的是,ThreadLocalMap 的每个 Entry 都是一个对 键 的弱引用,这一点从super(k)
可看出。另外,每个 Entry 都包含了一个对 值 的强引用。
使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,它可被回收,从而避免上文所述 ThreadLocal 不能被回收而造成的内存泄漏的问题。
但是,这里又可能出现另外一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。
适用场景:
- 每个线程需要有自己单独的实例
- 实例需要在多个方法中共享,但不希望被多线程共享