个人随笔--threadLocal应用实例

threadLoacl原理和源码 http://www.jasongj.com/java/threadlocal/

ThreadLocal具体原理

Thread维护ThreadLocal与实例的映射

   Thread 拥有 ThreadLocal.ThreadLocalMap  变量;

   线程访问ThreadLocal变量,只有通过ThreadLocal对象本身才能能够去ThreadLocalMap取出ThreadLocal变量的值;

// -----------------ThreadLocal.class----------------- 
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
 
// -----------------Thread.class----------------- 
ThreadLocal.ThreadLocalMap threadLocals = null;

 

ThreadLocal side Map

对于Thread 的 ThreadLocalMap 变量 , 它使用一个entry数组维护所有的ThreadLocal变量。

 

 int i = key.threadLocalHashCode & (table.length - 1);
 Entry e = table[i];

 

// -----------------ThreadLocalMap.class-------------------- 
private Entry getEntry(ThreadLocal key) { 
    int i = key.threadLocalHashCode & (table.length - 1); 
    Entry e = table[i]; 
    if (e != null && e.get() == key) 
        return e; 
    else 
        return getEntryAfterMiss(key, i, e); 
}

 

ThreadLocal维护线程与实例的映射(错误)

    这是一个抽象概念。threadLocal只是拥有一个拥有数据ThreadlocalMap内部类,但是真正拥有ThreadlocalMap的是thread;threalocal只是通过这个ThreadlocalMap访问数据而已;

ThreadLocal side Map

      这个图是错的。 

 

 

 

 

个人代码示例

打印的值,体现了threadlocal是线程私有的;但是不同值的threadlocal 的 hashcode的一致性,又印证了上面的理论;

threadlocal 类似  一个 去 threadlocalMap 取出value 的一把钥匙; 而并非threadlocal本身 有一个的map;

 

 
//			1   java.lang.ThreadLocal@2a60c715 ---1
//			3   java.lang.ThreadLocal@2a60c715 null
//			2   java.lang.ThreadLocal@2a60c715 null
	public static void main(String args[]) throws Exception {
 
		Thread a = new Thread(new Runnable(){
			@Override
			public void run(){
				TestThreadLocal test = new TestThreadLocal();
				test.set("---1");
				test.println("1   " + TestThreadLocal.threadLocal);
			}
		});
 
 
		Thread b = new Thread(new Runnable(){
			@Override
			public void run(){
				TestThreadLocal test = new TestThreadLocal();
//				test.set("2   ");
				test.println("2   " + TestThreadLocal.threadLocal);
			}
		});
 
		a.start();
		b.start();
 
		TestThreadLocal demo = new TestThreadLocal();
		demo.println("3   " + TestThreadLocal.threadLocal);
 
	}
 
 
	public static class TestThreadLocal {
		public static ThreadLocal threadLocal = new ThreadLocal<>();
 
		public void set(String str) {
			this.threadLocal.set(str);
		}
 
		public String get() {
			return this.threadLocal.get();
		}
 
		public void println(String flag) {
			System.out.println(flag + " " + get()); //append 暂时不用了
		}
 
//		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			set(threadName);
			println(threadName);
		}
	}
}

 

 

ThreadLocalMap与内存泄漏

该方案中,Map 由 ThreadLocal 类的静态内部类 ThreadLocalMap 提供。该类的实例维护某个 ThreadLocal 与具体实例的映射。与 HashMap 不同的是,ThreadLocalMap 的每个 Entry 都是一个对  的弱引用,这一点从super(k)可看出。另外,每个 Entry 都包含了一个对  的强引用。

1

2

3

4

5

6

7

8

9

static class Entry extends WeakReference> {
   /** The value associated with this ThreadLocal. */
 Object value;
 Entry(ThreadLocal k, Object v) {
      super(k);
 value = v;
 }
}
private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1);
 Entry e = table[i];
 if (e != null && e.get() == key)
        return e;
 else
 return getEntryAfterMiss(key, i, e);
}

 

ThreadLocal.ThreadLocalMap.entry  使用弱引用;

弱引用优点:

    使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,Object对象(key)可被回收,从而避免上文所述 ThreadLocal 不能被回收而造成的内存泄漏的问题。  

弱引用缺点:

     但是,这里又可能出现另外一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。

注:Entry虽然是弱引用,但它是 ThreadLocal 类型的弱引用(也即上文所述它是对  的弱引用),而非具体实例的的弱引用,所以无法避免具体实例相关的内存泄漏。

 

 

防止内存泄漏

对于已经不再被使用且已被回收的 ThreadLocal 对象,它在每个线程内对应的实例由于被线程的 ThreadLocalMap 的 Entry 强引用,无法被回收,可能会造成内存泄漏。

针对该问题,ThreadLocalMap 的 set 方法中,通过 replaceStaleEntry 方法将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。另外,会在 rehash 方法中通过 expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

private void set(ThreadLocal key, Object value) {
   Entry[] tab = table;
 int len = tab.length;
 int i = key.threadLocalHashCode & (len-1);
 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
      ThreadLocal k = e.get();
 if (k == key) {
         e.value = value;
 return;
 }
      if (k == null) {
         replaceStaleEntry(key, value, i);
 return;
 }
   }
   tab[i] = new Entry(key, value);
 int sz = ++size;
 if (!cleanSomeSlots(i, sz) && sz >= threshold)
      rehash();
}

 

 

适用场景

如上文所述,ThreadLocal 适用于如下两种场景

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

对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。

对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。

 

你可能感兴趣的:(个人随笔)