ThreadLocal简单理解

参考资料:http://www.cnblogs.com/dolphin0520/p/3920407.html
先来看看为什么用ThreadLocal,上一篇博客说的很好了,就好比,你要让你的线程链接数据库,如果你让这些线程共享一个数据库链接的话,就会出问题:
代码来自参考博客:

class ConnectionManager {

    private static Connection connect = null;

    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }

    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

当一个线程正在使用的时候,其他的线程可能会关闭这个链接,肯定就出问题了,要解决这个问题,你除了在每个线程单独的链接之外还可以使用ThreadLocal来解决这个问题。

我们把问题简化:我让三个线程随机输出5到1
先看代码:

class MyThread extends Thread {

    private int i = 5;

    @Override
    public void run() {
        for(int j = 0; j<20; j++) {
            synchronized (this) {
                if(i>0) {
                  System.out.println(                            
                  Thread.currentThread().getName() + ";" + i--);
                }
            }
        }
    }

}
public class TestMain {

    public static void main(String ar[]) {

        MyThread m1 = new MyThread(); 

        Thread mt1 = new Thread(m1);
        Thread mt2 = new Thread(m1);
        Thread mt3 = new Thread(m1);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

运行结果:

这里写图片描述

现在,我想让每个 线程都重5到1输出一遍,这个问题可以有其他的结局问题,这里讨论的是ThreadLocal,就用ThreadLocal解决
代码:

class MyThread extends Thread {

    private ThreadLocal<Integer> i = new ThreadLocal<Integer>();

    public void set() {
        i.set(5);
    }

    public void run() {
        set();
        int si = i.get();
        for(int j = 0; j<20; j++) {
            si = i.get();
            if(si>0) {
                System.out.println(Thread.currentThread().getName() + ";" + si-- );
                i.set(si);
            }
        }
    }

}
public class TestMain {

    public static void main(String ar[]) {

        MyThread m1 = new MyThread(); 

        Thread mt1 = new Thread(m1);
        Thread mt2 = new Thread(m1);
        Thread mt3 = new Thread(m1);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

结果:
这里写图片描述

这样就相当于每个线程内部都有一个 i 这个i 只有当前线程自己能访问,很多地方把ThreadLocal叫做线程本地变量的原因。
现在来看看ThreadLocal怎么保存变量副本的:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

这是ThreadLocal中经常用到的几个方法。
先看看get()

    /**
     * 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);
        //通过getMap(t)方法得到一个ThreadLocalMap
        if (map != null) {
        //如果map不为空,这将ThreadLocal作为键值,从map中取出一个Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
            //如果这个Entry不为空,就将他的值返回
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果当前map为null,或者entry为空,调用setInitialValue()方法
        return setInitialValue();
    }

    /*
     * 再来看看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);
        /*取出当前线程的map,如果为null则创建一个*/
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        /*这个方法最终会返回一个值,不管是创建一个map还是往里面放一个值*/
        return value;
    }

    /*最后来看看ThreadLocalMap ,这个map到底是什么*/
    /**
     * 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 values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
     /*threadLocals定义在,Thread.java中,这是Thread.java中的源码*/
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /**
     * 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中的一个内部类*/

现在理解ThreadLocal也就简单一点了:
首先,每个Thread中有一个ThreadLocal的内部类ThreadLocalMap的成员变量threadLocals ,这个threadLocals 就是来保存当前Thread的实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

再来说get(),get()会返回当前ThreadLocal为键的threadLocals中的值,
如果set()之前 get()这时候 会报空指针异常,(原因稍后再说)

然后看看set(T value)的代码:

    /**
     * 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);
    }

这时候看就容易了,
然后就是总结了:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

2)每个线程中可有多个threadLocal变量,记录不同的副本,类似id,name

3)在进行get之前,必须先set,否则会报空指针异常;

然后就是怎么处理这个空指针异常:

private T setInitialValue() {
        /*这个可以理解为得到一个值*/
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        /*取出当前线程的map,如果为null则创建一个*/
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        /*这个方法最终会返回一个值,不管是创建一个map还是往里面放一个值*/
        return value;
    }

如果你没有set()就调用get(),T value = initialValue();这个方法返回的应该是null,这就是原因。
所有如果你不想空指针异常就要重写这个方法:
例如:

class MyThread extends Thread {

    private ThreadLocal<Integer> i = new ThreadLocal<Integer>();

    public void set() {
        i.set(5);
    }

    public void run() {
       /*如果将这个 set()去掉,程序会空指针异常*/
       // set();
        int si = i.get();
        for(int j = 0; j<20; j++) {
            si = i.get();
            if(si>0) {
                System.out.println(Thread.currentThread().getName() + ";" + si-- );
                i.set(si);
            }
        }
    }

}
public class TestMain {

    public static void main(String ar[]) {

        MyThread m1 = new MyThread(); 

        Thread mt1 = new Thread(m1);
        Thread mt2 = new Thread(m1);
        Thread mt3 = new Thread(m1);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

这时如果自己重写initialValue()方法就可以避免:

class MyThread extends Thread {

    private ThreadLocal<Integer> i = new ThreadLocal<Integer>(){
         protected Integer initialValue() {
                return 5;
            };
    };

    public void set() {
        i.set(5);
    }

    public void run() {
    /*这时就去掉set()不会报错*/
    //set()
        int si = i.get();
        for(int j = 0; j<20; j++) {
            si = i.get();
            if(si>0) {
                System.out.println(Thread.currentThread().getName() + ";" + si-- );
                i.set(si);
            }
        }
    }

}
public class TestMain {

    public static void main(String ar[]) {

        MyThread m1 = new MyThread(); 

        Thread mt1 = new Thread(m1);
        Thread mt2 = new Thread(m1);
        Thread mt3 = new Thread(m1);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

结果:
这里写图片描述
今晚就写到这里了,如果有错误,欢迎讨论!以后要是深入研究会更新的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值