ThreadLocal

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\n",
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\n",
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 引用而无法被回收造成内存泄漏。

 

适用场景:

  • 每个线程需要有自己单独的实例
  • 实例需要在多个方法中共享,但不希望被多线程共享

 

转自:http://www.jasongj.com/java/threadlocal/

posted @ 2018-11-05 11:31  __Meng  阅读(344)  评论(0编辑  收藏  举报