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 

 

posted @ 2019-04-07 09:33  yvkm  阅读(285)  评论(0编辑  收藏  举报