zoukankan      html  css  js  c++  java
  • ThreadLocal

    ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程” 。其实,ThreadLocal并不是一个 Thread,而是 Thread 的局部变量,当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

    1. 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。 
    2. 将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
    3. 说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。 

    JDK 5 以后提供了泛型支持,ThreadLocal 被定义为支持泛型: 

    public class ThreadLocal<T> extends Object 
    T 为线程局部变量的类型。该类定义了 4 个方法: 
    1.  protected T initialValue():返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get()  方法访问变量时将调用此方法,但如果线程之前调用了 set(T)  方法,则不会对该线程再调用 initialValue  方法。通常,此方法对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。  该  实现返回 null;如果程序员希望线程局部变量具有 null  以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。      
    2. public T get():返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。 
    3. public void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,它们只依靠 initialValue()  方法来设置线程局部变量的值。
    4. public void remove():移除此线程局部变量当前线程的值。如果此线程局部变量随后被当前线程读取,且这期间当前线程没有设置其值,则将调用其 initialValue()  方法重新初始化其值。这将导致在当前线程多次调用 initialValue  方法。 

    那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }

    在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

    线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

     
    ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
    void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }

    代码已经说的非常直白,就是获取和设置Thread内的一个叫threadLocals的变量,而这个变量的类型就是ThreadLocalMap,这样进一步验证了上文中的观点:每个线程都有自己独立的ThreadLocalMap对象。打开java.lang.Thread类的源代码,我们能得到更直观的证明:

    那么接下来再看一下ThreadLocal类中的get()方法,代码是这么说的:

      public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
        }
    
       
        private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }

    这两个方法的代码告诉我们,在获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。

    进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。

    设置到这些线程中的隔离变量,会不会导致内存泄漏呢?ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将作为Thread实例的垃圾被回收掉,所以完全不用担心内存泄漏的问题。在多个线程中隔离的变量,光荣的生,合理的死,真是圆满,不是么?

    最后再提一句,ThreadLocal变量的这种隔离策略,也不是任何情况下都能使用的。如果多个线程并发访问的对象实例只允许,也只能创建那么一个,那就没有别的办法了,老老实实的使用同步机制来访问吧。

    下面是一个使用 ThreadLocal 的例子,每个线程产生自己独立的序列号。就是使用ThreadLocal存储每个线程独立的序列号复本,线程之间互不干扰。 
    package sync;  
    public class SequenceNumber {  
      // 定义匿名子类创建ThreadLocal的变量  
      private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  
       // 覆盖初始化方法  
       public Integer initialValue() {  
          return 0;  
      }  
     };  
      // 下一个序列号  
      public int getNextNum() {  
       seqNum.set(seqNum.get() + 1);  
       return seqNum.get();  
     }  
      private static class TestClient extends Thread {  
       private SequenceNumber sn;  
       public TestClient(SequenceNumber sn) {  
        this.sn = sn;  
      }  
       // 线程产生序列号  
       public void run() {  
        for (int i = 0; i < 3; i++) {  
        System.out.println("thread[" + Thread.currentThread().getName()   + "] sn[" + sn.getNextNum() + "]");  
       }  
      }  
     }  
      /** 
      * @param args 
      */  
      public static void main(String[] args) {  
      SequenceNumber sn = new SequenceNumber();  
          // 三个线程产生各自的序列号  
         TestClient t1 = new TestClient(sn);  
         TestClient t2 = new TestClient(sn);  
         TestClient t3 = new TestClient(sn);  
         t1.start();  
         t2.start();  
         t3.start();  
     }  
    }  
    程序的运行结果如下:  
    thread[Thread-1] sn[1]  
    thread[Thread-1] sn[2]  
    thread[Thread-1] sn[3]  
    thread[Thread-2] sn[1]  
    thread[Thread-2] sn[2]  
    thread[Thread-2] sn[3]  
    thread[Thread-0] sn[1] 
    thread[Thread-0] sn[2] thread[Thread-0] sn[3]
     从运行结果可以看出,使用了 ThreadLocal 后,每个线程产生了独立的序列号,没有相互干扰。通常我们通过匿名内部类的方式定义 ThreadLocal的子类,提供初始的变量值。 
            ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。 
     
            在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。 而 ThreadLocal 则从另一个角度来解决多线程的并发访问。ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。 
     
            概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal 采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。 
     
            需要注意的是 ThreadLocal 对象是一个本质上存在风险的工具,应该在完全理解将要使用的线程模型之后,再去使用 ThreadLocal 对象。这就引出了线程池(thread pooling)的问题,线程池是一种线程重用技术,有了线程池就不必为每个任务创建新的线程,一个线程可能会多次使用,用于这种环境的任何 ThreadLocal 对象包含的都是最后使用该线程的代码所设置的状态,而不是在开始执行新线程时所具有的未被初始化的状态。 那么 ThreadLocal 是如何实现为每个线程保存独立的变量的副本的呢?通过查看它的源代码,我们会发现,是通过把当前“线程对象”当作键,变量作为值存储在一个 Map 中。 
     
    private T setInitialValue() {  
            T value = initialValue();  
            Thread t = Thread.currentThread();  
            ThreadLocalMap map = getMap(t);  
            if (map != null)  
                map.set(this, value);  
            else  
                createMap(t, value);  
            return value;  
    }     
     总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点: 
             1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。 
             2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。 
     
             当然如果要把本来线程共享的对象通过ThreadLocal.set()放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中。 
     
              ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。 
     
     
             上面的这些资料,一部分是自己写的,一部分是参考别人的,然后自己改的.大家在学习的过程中如果有什么疑问也可以在这里提出来.
     
    public class ThreadLocal<T> {
        /**
         * ThreadLocals rely on per-thread hash maps attached to each thread
         * (Thread.threadLocals and inheritableThreadLocals).  The ThreadLocal
         * objects act as keys, searched via threadLocalHashCode.  This is a
         * custom hash code (useful only within ThreadLocalMaps) that eliminates
         * collisions in the common case where consecutively constructed
         * ThreadLocals are used by the same threads, while remaining well-behaved
         * in less common cases.
         */
        private final int threadLocalHashCode = nextHashCode();
    
        /**
         * The next hash code to be given out. Accessed only by like-named method.
         */
        private static int nextHashCode = 0;
    
        /**
         * The difference between successively generated hash codes - turns
         * implicit sequential thread-local IDs into near-optimally spread
         * multiplicative hash values for power-of-two-sized tables.
         */
        private static final int HASH_INCREMENT = 0x61c88647;
    
        /**
         * Compute the next hash code. The static synchronization used here
         * should not be a performance bottleneck. When ThreadLocals are
         * generated in different threads at a fast enough rate to regularly
         * contend on this lock, memory contention is by far a more serious
         * problem than lock contention.
         */
        private static synchronized int nextHashCode() {
            int h = nextHashCode;
            nextHashCode = h + HASH_INCREMENT;
            return h;
        }
    
        /**
         * Creates a thread local variable.
         */
        public ThreadLocal() {
        }
    
        /**
         * Returns the value in the current thread's copy of this thread-local
         * variable.  Creates and initializes the copy if this is the first time
         * the thread has called this method.
         *
         * @return the current thread's value of this thread-local
         */
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                return (T)map.get(this);
    
            // Maps are constructed lazily.  if the map for this thread
            // doesn't exist, create it, with this ThreadLocal and its
            // initial value as its only entry.
            T value = initialValue();
            createMap(t, value);
            return value;
        }
    
        /**
         * Sets the current thread's copy of this thread-local variable
         * to the specified value.  Many applications will have no need for
         * this functionality, relying solely on the {@link #initialValue}
         * method to set the values of thread-locals.
         *
         * @param value the value to be stored in the current threads' copy of
         *        this thread-local.
         */
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
        /**
         * Get the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param  t the current thread
         * @return the map
         */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
        /**
         * Create the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the map
         * @param map the map to store.
         */
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
        .......
    
        /**
         * ThreadLocalMap is a customized hash map suitable only for
         * maintaining thread local values. No operations are exported
         * outside of the ThreadLocal class. The class is package private to
         * allow declaration of fields in class Thread.  To help deal with
         * very large and long-lived usages, the hash table entries use
         * WeakReferences for keys. However, since reference queues are not
         * used, stale entries are guaranteed to be removed only when
         * the table starts running out of space.
         */
        static class ThreadLocalMap {
    
        ........
    
        }
    
    }
  • 相关阅读:
    数据库回滚解决删除数据库出错
    教大家支付宝抢红包
    MySql 连接字符串
    搭建Git服务器
    队列的顺序存储结构
    栈的应用---递归
    栈的链式存储结构及应用(C、Java代码)
    栈的顺序存储结构及应用(C、Java代码)
    静态链表、循环链表、双向链表(C代码实现)
    线性表的链式存储结构(Java代码实现)
  • 原文地址:https://www.cnblogs.com/mingfeng002/p/3509257.html
Copyright © 2011-2022 走看看