文章导读:
早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序, 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本, 每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本.
视频与源码下载:http://edu.51cto.com/lecturer/index/user_id-9166337.html (代码在视频的附件中)
先来用代码来演示下ThreadLocal强大特性
1 // 用来来存储与线程相关的变量 2 public class ThreadLocDemo { 3 // 用来存储线程名称 4 private ThreadLocal<String> ts = new ThreadLocal<String>(); 5 // 用来存储线程的ID 6 private ThreadLocal<Long> tl = new ThreadLocal<Long>(); 7 8 // 获取当前线程的名称和ID信息 9 public void set() { 10 ts.set(Thread.currentThread().getName()); 11 tl.set(Thread.currentThread().getId()); 12 } 13 14 public String getName() { 15 return ts.get(); 16 } 17 18 public Long getId() { 19 return tl.get(); 20 } 21 22 public static void main(String[] args) throws Exception { 23 // 当前线程是主线程 24 // System.out.println(Thread.currentThread().getName()); 25 // System.out.println(Thread.currentThread().getId()); 26 27 // 把主线程的信息存储在ThreadLocal中 28 final ThreadLocDemo demo = new ThreadLocDemo(); 29 demo.set(); 30 // 输出主线程的信息 31 System.out.println(demo.getName()); 32 System.out.println(demo.getId()); 33 34 // 创建一个子线程,然后用demo也存储子线程的信息 35 new Thread(new Runnable() { 36 public void run() { 37 // 把子线程的信息存储在ThreadLocal中 38 demo.set(); 39 // 输出子线程的信息 40 System.out.println(demo.getName()); 41 System.out.println(demo.getId()); 42 } 43 }, "xyz").start(); 44 45 Thread.sleep(1000); 46 // 输出主线程的信息 47 System.out.println(demo.getName()); 48 System.out.println(demo.getId()); 49 } 50 }
从打印结果可以看出主线程与子线程数据的存储,获取是不冲突的
main 1 xyz 9 main 1
ThreadLocal.set源码分析, 通过源码可以看出来,Local为不同的线程创建了自己的ThreadLocalMap对象.数据本质是存储在ThreadLocalMap中
1 public void set(T value) { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) 5 map.set(this, value); 6 else 7 createMap(t, value); 8 }
ThreadLocalMap存储结构分析
1 static class ThreadLocalMap { 2 // 存储数据的类型是弱引用 3 static class Entry extends WeakReference<ThreadLocal> { 4 5 Object value; 6 Entry(ThreadLocal k, Object v) { 7 super(k); 8 value = v; 9 } 10 } 11 }
ThreadLocal.get()源码分析. 其实还是先通过线程获取每个线程自己的LocalMap对象,然后从Map对象中获取数据信息
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 return (T)e.value; 8 } 9 return setInitialValue(); 10 }
总结, 大家注意三点:
- 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象
- 将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中
- ThreadLocal 不是用来解决共享对象的多线程访问问题的, 它是用来延迟变量的生命周期,后面的事务、缓存都会用到此API