Java并发编程实践 目录
并发编程 04—— 闭锁CountDownLatch 与 栅栏CyclicBarrier
并发编程 06—— CompletionService : Executor 和 BlockingQueue
并发编程 10—— 任务取消 之 关闭 ExecutorService
并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略
并发编程 20—— AbstractQueuedSynchronizer 深入分析
概述
第1 部分 ThreadLocal是什么
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。ThreadLocal功能非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且ThreadLocal实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
第2 部分 ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下。
void set(Object value)
- 设置当前线程的线程局部变量的值;
public Object get()
- 该方法返回当前线程所对应的线程局部变量;
public void remove()
- 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
protected Object initialValue()
- 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null。
set 方法:
1 /** 2 * Sets the current thread's copy of this thread-local variable 3 * to the specified value. Most subclasses will have no need to 4 * override this method, relying solely on the {@link #initialValue} 5 * method to set the values of thread-locals. 6 * 7 * @param value the value to be stored in the current thread's copy of 8 * this thread-local. 9 */ 10 public void set(T value) { 11 // 获取当前线程对象 12 Thread t = Thread.currentThread(); 13 // 获取当前线程本地变量Map 14 ThreadLocalMap map = getMap(t); 15 // map不为空 16 if (map != null) 17 // 存值 18 map.set(this, value); 19 else 20 // 创建一个当前线程本地变量Map 21 createMap(t, value); 22 } 23 24 /** 25 * Get the map associated with a ThreadLocal. Overridden in 26 * InheritableThreadLocal. 27 * 28 * @param t the current thread 29 * @return the map 30 */ 31 ThreadLocalMap getMap(Thread t) { 32 // 获取当前线程的本地变量Map 33 return t.threadLocals; 34 }
这里注意,ThreadLocal中是有一个Map,但这个Map不是我们平时使用的Map,而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的一个内部类,不对外使用的。当使用ThreadLocal存值时,首先是获取到当前线程对象,然后获取到当前线程本地变量Map,最后将当前使用的ThreadLocal和传入的值放到Map中,也就是说ThreadLocalMap中存的值是[ThreadLocal对象, 存放的值],这样做的好处是,每个线程都对应一个本地变量的Map,所以一个线程可以存在多个线程本地变量。
get方法:
1 /** 2 * Returns the value in the current thread's copy of this 3 * thread-local variable. If the variable has no value for the 4 * current thread, it is first initialized to the value returned 5 * by an invocation of the {@link #initialValue} method. 6 * 7 * @return the current thread's value of this thread-local 8 */ 9 public T get() { 10 Thread t = Thread.currentThread(); 11 ThreadLocalMap map = getMap(t); 12 if (map != null) { 13 ThreadLocalMap.Entry e = map.getEntry(this); 14 if (e != null) 15 return (T)e.value; 16 } 17 // 如果值为空,则返回初始值 18 return setInitialValue(); 19 }
有了之前set方法的分析,get方法也同理,需要说明的是,如果没有进行过set操作,那从ThreadLocalMap中拿到的值就是null,这时get方法会返回初始值,也就是调用initialValue()方法,ThreadLocal中这个方法默认返回null。当我们有需要第一次get时就能得到一个值时,可以继承ThreadLocal,并且覆盖initialValue()方法。
第3 部分 一个TheadLocal实例
1 /** 2 * 3 * @ClassName: SequenceNumber 4 * @author xingle 5 * @date 2015-3-9 上午9:54:23 6 */ 7 public class SequenceNumber { 8 //①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 9 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){ 10 public Integer initialValue(){ 11 return 0; 12 } 13 }; 14 15 //②获取下一个序列值 16 public int getNextNum(){ 17 seqNum.set(seqNum.get()+1); 18 return seqNum.get(); 19 } 20 21 public static void main(String[] args){ 22 23 SequenceNumber sn = new SequenceNumber(); 24 //③ 3个线程共享sn,各自产生序列号 25 TestClient t1 = new TestClient(sn); 26 TestClient t2 = new TestClient(sn); 27 TestClient t3 = new TestClient(sn); 28 t1.start(); 29 t2.start(); 30 t3.start(); 31 } 32 33 private static class TestClient extends Thread{ 34 private SequenceNumber sn; 35 public TestClient(SequenceNumber sn){ 36 this.sn = sn; 37 } 38 39 public void run(){ 40 //④每个线程打出3个序列值 41 for (int i = 0 ;i<3;i++){ 42 System.out.println("thread["+Thread.currentThread().getName()+"] sn["+sn.getNextNum()+"]"); 43 } 44 } 45 } 46 47 }
通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:
每个线程所产生的序号虽然都共享同一个Sequence Number实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。
2.Java线程(篇外篇):线程本地变量ThreadLocal
3.线程本地变更,即ThreadLocal-->Spring事务管理