Java中ThreadLocal类(解决多线程程序中并发问题的一种新思路,主要为参数的拷贝问题)

来源:互联网 发布:网络信息储存注意事项 编辑:程序博客网 时间:2024/06/05 11:27

ThreadLocal类是我在研究源码时候发现的,经过深入的学习研究,越发感慨其设计的伟大之处。感谢网上的一些大神的解释帮助,在此向大神们致敬,本文是在借鉴网上的一些现有资料并结合我对源码的一些学习理解而做的总结,与有心人一起共享学习。


百度百科的解释(部分摘抄)        

        JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量
        ThreadLocal主要由四个方法组成initialValue(),get(),set(T),remove(),其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第1次调用get()时才执行,并且仅执行1次(即:最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用get()方法访问变量的时候。如果线程先于get方法调用set(T)方法,则不会在线程中再调用initialValue方法)。ThreadLocal中的缺省实现直接返回一个null。
        下面我们看一下百度百科给出的一个例子(有部分代码改动):
import java.sql.Connection;import java.sql.Statement;public class TopicDao {// 1.使用ThreadLocal保存Connection变量private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();public static Connection getConnection() {// 2.如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,并将其保存到线程本地变量中。if (connThreadLocal.get() == null) {Connection conn = ConnectionManager.getConnection();connThreadLocal.set(conn);return conn;} else {// 3.直接返回线程本地变量return connThreadLocal.get();}}public void addTopic() {// 4.从ThreadLocal中获取线程对应的ConnectionStatement stat = getConnection().createStatement();}}

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
        在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
        而ThreadLocal则从另一个角度来解决多线程的并发访问。在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
        由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。
        概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

ThreadLocal的一个使用简单例子
        ThreadLocal的典型的用法是调用一个静态方法,这个静态方法会操作一个ThreadLocal变量,而不同的线程去调用时,访问的就是不同的副本。
        模拟一个游戏,预先随机设定一个[1, 10]的整数,然后每个玩家去猜这个数字,每个玩家不知道其他玩家的猜测结果,看谁用最少的次数猜中这个数字。这里可以把每个玩家作为一个线程,然后用ThreadLocal来记录玩家猜测的历史记录,这样就很容易理解ThreadLocal的作用。

Judge:用来设定目标数字以及判断猜测的结果。
Player:每个Player作为一个线程,多个Player并行地去尝试猜测,猜中时线程终止。
Attempt:具有ThreadLocal字段和猜测动作静态方法的类,ThreadLocal用于保存猜过的数字。
Record:保存历史记录的数据结构,有一个List<Integer>字段。

        ThreadLocal为了可以兼容各种类型的数据,实际的内容是再通过set和get操作的对象,详见Attempt的getRecord()。运行的时候,每个Player Thread都是去调用Attemp.guess()方法,进而操作同一个ThreadLocal变量history,但却可以保存每个线程自己的数据,这就是ThreadLocal的作用。
import java.util.ArrayList;import java.util.List;import java.util.Random;public class ThreadLocalTest {        public static void main(String[] args) {        Judge.prepare();        new Player(1).start();        new Player(2).start();        new Player(3).start();    }    }class Judge {        public static int MAX_VALUE = 10;    private static int targetValue;        public static void prepare() {        Random random = new Random();        targetValue = random.nextInt(MAX_VALUE) + 1;    }        public static boolean judge(int value) {        return value == targetValue;    }    }class Player extends Thread {        private int playerId;        public Player(int playerId) {        this.playerId = playerId;    }        @Override    public void run() {        boolean success = false;        while(!success) {            int value = Attempt.guess(Judge.MAX_VALUE);            success = Judge.judge(value);            System.out.println(String.format("Plyaer %s Attempts %s and %s", playerId, value, success ? " Success" : "Failed"));        }        Attempt.review(String.format("[IFNO] Plyaer %s Completed by ", playerId));    }    }class Attempt {        private static ThreadLocal<Record> history = new ThreadLocal<Record>();        public static int guess(int maxValue) {        Record record = getRecord();        Random random = new Random();        int value = 0;        do {            value = random.nextInt(maxValue) + 1;        } while (record.contains(value));        record.save(value);        return value;    }        public static void review(String info) {        System.out.println(info + getRecord());    }        private static Record getRecord() {        Record record = history.get();        if(record == null) {            record = new Record();            history.set(record);        }        return record;    }    }class Record {        private List<Integer> attemptList = new ArrayList<Integer>();;        public void save(int value) {        attemptList.add(value);    }        public boolean contains(int value) {        return attemptList.contains(value);    }        @Override    public String toString() {        StringBuffer buffer = new StringBuffer();        buffer.append(attemptList.size() + " Times: ");        int count = 1;        for(Integer attempt : attemptList) {            buffer.append(attempt);            if(count < attemptList.size()) {                buffer.append(", ");                count++;            }        }        return buffer.toString();    }    }
结果:
Plyaer 2 Attempts 8 and FailedPlyaer 3 Attempts 6 and FailedPlyaer 1 Attempts 5 and FailedPlyaer 2 Attempts 7 and  SuccessPlyaer 3 Attempts 9 and FailedPlyaer 1 Attempts 9 and FailedPlyaer 3 Attempts 2 and FailedPlyaer 1 Attempts 2 and Failed[IFNO] Plyaer 2 Completed by 2 Times: 8, 7Plyaer 3 Attempts 4 and FailedPlyaer 1 Attempts 1 and FailedPlyaer 3 Attempts 5 and FailedPlyaer 1 Attempts 3 and FailedPlyaer 3 Attempts 1 and FailedPlyaer 1 Attempts 10 and FailedPlyaer 3 Attempts 8 and FailedPlyaer 1 Attempts 6 and FailedPlyaer 3 Attempts 7 and  SuccessPlyaer 1 Attempts 4 and Failed[IFNO] Plyaer 3 Completed by 8 Times: 6, 9, 2, 4, 5, 1, 8, 7Plyaer 1 Attempts 7 and  Success[IFNO] Plyaer 1 Completed by 9 Times: 5, 9, 2, 1, 3, 10, 6, 4, 7



分析一下ThreadLocal的工作原理

         ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其它线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。 
         另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。  
        下面先看一下value的设置过程。摘抄ThreadLocal中部分代码:
     /**     * Sets the current thread's copy of this thread-local variable     * to the specified value.  Most subclasses will have no need to     * override this method, relying solely on the {@link #initialValue}     * method to set the values of thread-locals.     *     * @param value the value to be stored in the current thread's 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);    }     /**     * 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);    }

ThreadLocal获得是通过当前线程并把ThreadLocal作为key值获得,如若ThreadLocalMap返回null,将重新创建一个并返回。摘抄ThreadLocal中部分代码: 
     /**     * Returns the value in the current thread's copy of this     * thread-local variable.  If the variable has no value for the     * current thread, it is first initialized to the value returned     * by an invocation of the {@link #initialValue} 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) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue();    }    /**     * Variant of set() to establish initialValue. Used instead     * of set() in case user has overridden the set() method.     *     * @return the initial value     */    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的应用场合,最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。 
        ThreadLocalMap是属于ThreadLocal的内部类。摘抄ThreadLocal中部分代码: 
 /**     * 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 {        /**         * The entries in this hash map extend WeakReference, using         * its main ref field as the key (which is always a         * ThreadLocal object).  Note that null keys (i.e. entry.get()         * == null) mean that the key is no longer referenced, so the         * entry can be expunged from table.  Such entries are referred to         * as "stale entries" in the code that follows.         */        static class Entry extends WeakReference<ThreadLocal> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal k, Object v) {                super(k);                value = v;            }        }       ...    }

        ThreadLocalMap的声明并不是在ThreadLocal中,研究源码可发现真正用到ThreadLocalMap的是线程类Thread。下面是Thread源码的部分摘抄:
     /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;    /*     * InheritableThreadLocal values pertaining to this thread. This map is     * maintained by the InheritableThreadLocal class.     */    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
而TheadLocal获得TheadLocalMap是通过get方法,该方法通过调用当前线程而获得threadLocalMap变量,进而把当前ThreadLocal作为对应的key获得对应的value(间接说明了ThreadLocal是线程局部变量的说法)。下面摘抄ThreadLocal中部分代码:
     /**     * Returns the value in the current thread's copy of this     * thread-local variable.  If the variable has no value for the     * current thread, it is first initialized to the value returned     * by an invocation of the {@link #initialValue} 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) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue();    }     /**     * 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;    }
至此,我们可以看到ThreadLocal工具类的工作原理:对于需要同步线程的变量,不同的线程之间通过变量拷贝(即new新实例的方式)放在自身线程的ThreadLocalMap中,从而使不同线程之间拥有自己独立的变量副本;而变量的获取也是通过查找当前自身线程中的ThreadLocalMap从而获得对应的变量。其中该变量可以是任意类型存储在ThreadLocalMap的value中,而由于ThreadLocal往往作为ThreadLocalMap的key,因此ThreadLocal在实际运用中被定义为static类型。


        TheadLocal的实际应用
        TheadLocal作为解决多线程中并发问题的一种解决方案,在各大框架中具有较多的运用(如struts、spring等)。
        我们知道,一次浏览器的请求时”同一线程贯通三层“的方式,即一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程。通过TheadLocal就能很好的解决线程之间的数据副本问题。
        例如:Spring使用ThreadLocal解决线程安全问题。在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,这样有状态的Bean就可以在多线程中共享了。

        下面我们看一下ActionContext中对ThreadLocal的运用,我们知道ActionContext(com.opensymphony.xwork.ActionContext)是Action执行时的上下文,上下文可以看作是一个容器,它存放的是Action在执行时需要用到的对象,比如:在使用WebWork时,我们的上下文放有请求的参数(Parameter)、会话(Session)、Servlet上下文(ServletContext)、本地化(Locale)信息等。ActionContext就是通过ThreadLocal保证线程安全的,在每次执行Action之前都会创建新的ActionContext,ActionContext是线程安全的,也就是说在同一个线程里ActionContext里的属性是唯一的,这样我的Action就可以在多线程中使用。下面源码摘抄至ActionContext类:
        ActionContext中的两个变量
static ThreadLocal actionContext = new ThreadLocal();Map<String, Object> context;
 这里把actionContext声明为static类型,用于线程安全维护;而context用于存储对应的数据信息。其中context的获得代码如下
public static ActionContext getContext()  {    return (ActionContext)actionContext.get();  }
        下面简单举例其用法(主要用于获得用户请求参数):
package com.haiwi.util;import java.util.Map;import com.opensymphony.xwork2.ActionContext;public class ActionUtil {public static String getRequestParameter(String name){ActionContext ctx=ActionContext.getContext();Map<String,Object> map = ctx.getParameters();String[] para = (String[])map.get(name);return String.valueOf(para[0]);}}

相关源码链接地址:http://download.csdn.net/detail/otengyue/7734637

0 0
原创粉丝点击