关闭

ThreadLocal简单理解

标签: 线程
155人阅读 评论(0) 收藏 举报
分类:

参考资料: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
查看评论

ThreadLocal理解与使用

在看FrameWork源码时,在ActivityThread类中有一个ThreadLocal变量,是这么定义的: static final ThreadLocal sThreadLocal = new ThreadLocal();      再加之,项目中和其...
  • u012481172
  • u012481172
  • 2015-09-01 13:12
  • 624

线程学习总结(volatile、synchronized、ThreadLocal)

1、线程内存访问机制         当线程访问某个对象的值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任...
  • zzh87615
  • zzh87615
  • 2014-12-15 11:27
  • 2021

ThreadLocal源码的一些理解

ThreadLocal在工作中经常使用,尤其在Web的请求周期中,常见的使用场景如在SpringMvc中,从登录拦截器中preHandler中set一个全局的变量,在postHanlder时remove,这里很多使用容易忽略remove,因为大多数Web服务器是使用线程池工作的,如果在一个reque...
  • kimichen123
  • kimichen123
  • 2017-01-20 13:37
  • 159

谈谈ThreadLocal和解决线程安全的关系

在这篇文章中我粗略的就我的理解谈了一下ThreadLocal。但是很多时候我们还是会认为ThreadLocal是为了解决线程安全的问题而设计的。这篇文章就我的理解再加上该文章  中很多朋友的回复阐述一下ThreadLocal和线程安全的关系。  首先我们来看一下线程安全问...
  • chengwai26
  • chengwai26
  • 2016-08-09 18:38
  • 1732

Java-ThreadLocal的简单例子

内容:ThreadLocal:允许我们创建只能被同一个线程读写的变量,例如Web应用中将变量从前端到后台,并且需要在这次请求的线程中始终可以随时获取到。内部实现是通过一个ThreadLocalMap这个Map结构来实现的,将线程对象作为Key,变量副本作为Value。 public class Te...
  • u011345136
  • u011345136
  • 2015-06-02 22:53
  • 1436

简单理解ThreadLocal

简单理解ThreadLocal
  • jiangxiaoyuhh
  • jiangxiaoyuhh
  • 2015-08-03 22:59
  • 103

笔记-ThreadLocal简单理解

这几天翻阅ThreadLocal的资料,以及翻阅了jdk源码,下面整理一下思绪说说我的理解。 就像字面意思一样,ThreadLocal就是线程本地的意思,他存在的意义是为每个线程存储单独的变量,该变量在线程内可见。 网上很多资料有提到说也是为了线程完全问题,这么说其实也是对的,因为他确实可以避免...
  • helianus
  • helianus
  • 2017-09-17 13:19
  • 64

Java学习整理系列之ThreadLocal的理解

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而ThreadLocal则从另一个角度来解决多线程的并发访问。Thr...
  • Sup_Heaven
  • Sup_Heaven
  • 2014-06-11 20:30
  • 5382

ThreadLocal的简单理解

一.基本介绍 谷歌开发文档上的介绍:Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLoc...
  • u011889786
  • u011889786
  • 2016-01-29 14:28
  • 194

多线程中ThreadLocal的理解

1、理解ThreadLocal 在多线程开发的过程中可能会有这样的需求,有些变量或者对象在同一个线程中是共享的,在不同的线程中是隔离的,如何实现? (1)下面的这个例子是演示了在多线程的环境下不同的业务对象使用相同的对象数据时出现了错乱:public class ThreadSingleton ...
  • eff666
  • eff666
  • 2016-09-04 16:07
  • 455
    个人资料
    • 访问:8454次
    • 积分:312
    • 等级:
    • 排名:千里之外
    • 原创:23篇
    • 转载:2篇
    • 译文:0篇
    • 评论:3条
    文章分类