• ThreadLocal的意义和实现


    可以想像,如果一个对象的可变的变量被多个线程访问时,必然是不安全的。

      在单线程应用可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都传递一个Connection对象。ThreadUnsafe类就是这样做的:

    public class ThreadUnsafe {
         private static Connection connection = DriverManager.getConnection(DB_URL);
        
         public void Connection getConnection{  /* 在多线程应用中,connection 在被多个线程访问 */
            return connection;
        }
    }

      但是JDBC连接对象不一定是线程安全的,在多个线程访问到Connection时,就可能出现安全问题。为了解决这个问题,ThreadLocal类提供了安全的做法。

      通过将JDBC的Connection对象封装在ThreadLocal对象中,当每个线程访问需要Connection对象时,ThreadLocal对象返回的是一个副本。

    public class ThreadUnsafe {
         private static ThreadLocal<Connection> connectionHodler = new ThreadLocal<>{
             public Connection initialValue() {
                 return DriverManager.getConnection(DB_URL);
             }
         } 
        
         public void Connection getConnection{ /* 即使多个线程可以访问,依然安全 */
            return connectionHolder.get();
        }
    }

    ThreadLocal是如何实现这种功能? 

      首先,在Thread类中有一个threadLocals的实例变量,这是一个Map,保存了与线程相关的ThreadLocal对象封装的变量。

     /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;

      当线程初次调用ThreadLocal对象的get方法时,就会调用initialValue()来获取初始值。

        /**
         * 返回ThreadLocal封装的对象。*/
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) { /* 首次调用map为null */
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue(); /* 首次调用的返回值 */
        }
    
        /**
         * 初始化封装在ThreadLocal中对象的值。*/
        private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value); //为什么键值是ThreadLocal对象?,因为一个线程对象可能有使用多个ThreadLocal封闭的变量
            else
                createMap(t, value);
            return value;
        }
    
        /**
         * 更新封装在ThreadLocal中对象的值*/
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
      
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
        /**
         * 创建一个Map,用于保存ThreadLocal和其封装的对象。*/
        void createMap(Thread t, T firstValue) { 
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }

      注意:ThreadLocalMap在ThreadLocal类中声明,却是在Thread类中使用的,原因在于,当线程结束时,这些特定于线程的值保存在Thread对象中,当线程终止后,这些值会作为垃圾回收。

      ThreadLocal类实现的是一种线程封闭技术。将变量封闭在单线程中,从而避免同步。

    参考: 《Java Concurrency in Practice》 P35&P37 

  • 相关阅读:
    SPOJ GSS4 Can you answer these queries IV ——树状数组 并查集
    SPOJ GSS3 Can you answer these queries III ——线段树
    SPOJ GSS2 Can you answer these queries II ——线段树
    SPOJ GSS1 Can you answer these queries I ——线段树
    BZOJ 2178 圆的面积并 ——Simpson积分
    SPOJ CIRU The area of the union of circles ——Simpson积分
    HDU 1724 Ellipse ——Simpson积分
    HDU 1071 The area ——微积分
    HDU 4609 3-idiots ——FFT
    BZOJ 2194 快速傅立叶之二 ——FFT
  • 原文地址:https://www.cnblogs.com/yvkm/p/10664109.html
Copyright © 2020-2023  润新知