深入理解ThreadLocal

ThreadLocal是一个和线程安全相关的类。

一个非线程安全的例子

在我们讲述它之前,我们先看一个例子。

package thread;

public class NotSafeThread implements Runnable{
    private int a=10;

    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 5; i++) {

            try {
                if (Thread.currentThread().toString()
                        .equals("Thread[t2,5,main]")
                        && i == 2)
                    Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().toString() + "   a="
                    + a++);
        }

    }
}
package thread;
public class TestThread {
    public static void main(String[] args) {
        NotSafeThread nst=new NotSafeThread();
        Thread t1=new Thread(nst,"t1");
        Thread t2=new Thread(nst,"t2");
        Thread t3=new Thread(nst,"t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

输出的结果:a在各个线程中会累积。

Thread[t1,5,main]   a=10

Thread[t1,5,main]   a=11

Thread[t1,5,main]   a=12

Thread[t1,5,main]   a=13

Thread[t1,5,main]   a=14

Thread[t2,5,main]   a=15

Thread[t2,5,main]   a=16

Thread[t3,5,main]   a=17

Thread[t3,5,main]   a=18

Thread[t3,5,main]   a=19

Thread[t3,5,main]   a=20

Thread[t3,5,main]   a=21

Thread[t2,5,main]   a=22

Thread[t2,5,main]   a=23

Thread[t2,5,main]   a=24

解决这个问题,有各种办法例如把a移动到run方法里面,给run加上synchronized等等。

不过为了解决这个问题我们还有一种方式:使用ThreadLocal来维护a。

ThreadLocal的简单介绍

  早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

我们先看应用再讲原理,然后再讲一个实际的应用。

第一个应用

public class SafaThread {
    private static ThreadLocal<Integer> counterContext=new ThreadLocal<Integer>();

    public static Integer getCounterContext(){
        if (counterContext.get()!=null)
            return counterContext.get();
        setCounterContext(10);
        return counterContext.get();
    }
    public static void setCounterContext(Integer value){
        counterContext.set(value);
    }

    public static Integer getCounterContextNext(){
        counterContext.set(getCounterContext()+1);
        return counterContext.get();
    }

}
package thread;

public class ThreadLocalTest extends Thread{
    public void run() {
        for (int i = 0; i < 5; i++)
            System.out.println(Thread.currentThread().getName()+"   a="+
            SafaThread.getCounterContextNext());
    }

    public static void main(String[] args) {
        ThreadLocalTest t=new ThreadLocalTest();
        Thread t1=new Thread(t);
        Thread t3=new Thread(t);
        Thread t2=new Thread(t);

        t1.start();
        t2.start();
        t3.start();
    }
}

结果如下:

Thread-1   a=11

Thread-1   a=12

Thread-1   a=13

Thread-1   a=14

Thread-1   a=15

Thread-3   a=11

Thread-3   a=12

Thread-3   a=13

Thread-3   a=14

Thread-3   a=15

Thread-2   a=11

Thread-2   a=12

Thread-2   a=13

Thread-2   a=14

Thread-2   a=15

完全符合我们的要求。

现在重头戏来了,看看ThreadLocal实现的原理。

ThreadLocal类方法很简单,只有4个方法,我们先来了解一下:

void set(Object value)设置当前线程的线程局部变量的值。

public Object get()该方法返回当前线程所对应的线程局部变量。

public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

  值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

除了上面的方法介绍外,ThreadLocal里还有一个静态类:

    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
    ......//省略其他代码
}

ThreadLocalMap里面还有一个静态类,也是醉了..静态类是Entry。

Entry里面存放的是当前的ThreadLocal和这个线程中的成员变量。

当前线程的成员变量?怎么来的?

看这个:

public class Thread implements Runnable {

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

看到了吧,线程这个基类本身里面就有一个ThreadLocalMap。

好啦 现在我们开启debug模式。

System.out.println(Thread.currentThread().getName()+"   a="+
        SafaThread.getCounterContextNext());

ok,进去

    public static Integer getCounterContextNext(){
        counterContext.set(getCounterContext()+1);
        return counterContext.get();
    }

最先执行的是getCounterContext()。

    public static Integer getCounterContext(){
        if (counterContext.get()!=null)
            return counterContext.get();
        setCounterContext(10);
        return counterContext.get();
    }

我们看看counterContext(它本身是ThreadLocal<Integer>)的get方法。如下:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);   //标注1
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); //标注3
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

当我们第一次调用SafaThread.getCounterContextNext()的时候,直到上面的标注1处,返回的map肯定为空。我们看看setInitialValue

    private T setInitialValue() {
        T value = initialValue();     //标注2
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

至于creatMap里面的代码

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

我们再看看ThreadLocalMap的构造函数。你妹呀,你想累死老子,都最后一步了,自己看!

看看上面的标注2

    protected T initialValue() {
        return null;
    }

现在我们看看,看看什么?看ThreadLocal的set方法。中间的我不讲了。太累了。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

跟get一样是吧。

这下大家都明白了吧。

我说几个关键点

ThreadLocalMap里面有个Entry数组,数组里面放的元素由两部分组成

        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

key是ThreadLocal,value是要存储的值。

key为什么不是当前线程?而是ThreadLocal呢?

自己想。想不明白再问我。

在上面的情况下,一个线程里的ThreadLocalMap的Entry的size只能是1。

为什么?看ThreadLocal的set方法。

一个问题

public class SafaThread {
    private static ThreadLocal<Integer> counterContext=new ThreadLocal<Integer>();
    private static ThreadLocal<Integer> counterContext2=new ThreadLocal<Integer>();
    ...//省略其他代码 本来有三个方法 现在就是六个
}

本来只有一个变量,我现在放两个。

会怎么样。

答案就是:

key为什么不是线程?而是ThreadLocal呢?

Entry各个元素的区别到底在哪?

标注3的this是什么?

亲 你懂了没?

好吧,我偷的一手好懒。

下一个应用

我们看看struts中是如何运用ThreadLocal的。

感谢glt

总结

ThreadLocal最适合使用的场景:

在同一个线程的不同开发层次共享数据

使用ThreadLocal的步骤

1建立一个类,在其中封装静态的ThreadLocal变量,使其成为一个数据共享环境

2在类中实现ThreadLoca变量的静态方法(设值与取值)

参考资料

先看a再看b

a:  http://lavasoft.blog.51cto.com/62575/51926/

b:  http://blog.csdn.net/lufeng20/article/details/24314381struts2技术内幕

c:  struts2技术内幕  4.1节

时间: 2024-10-12 10:32:33

深入理解ThreadLocal的相关文章

一个例子理解threadLocal用法

ThreadLocal可以使对象达到线程隔离的目的.话不多说直接上代码: /** * 一个例子理解threadLocal * * 一个单例模式的类 */ public class SingleThreadLocalTest { private static SingleThreadLocalTest single = new SingleThreadLocalTest(); private ThreadLocal<String> threadLocal = new ThreadLocal<

理解ThreadLocal背后的概念

介绍 我之前在任何场合都没有使用过thread local,因此没有注意到它,直到最近用到它的时候. 前提信息 线程可以理解为一个单独的进程,它有自己的调用栈.在java中每一个线程都有一个调用栈或者说每一个调用栈都有一个线程,即使你不在你的程序中创建线程,线程仍然会在你不知道的情况下运行.最简单的例子就是,当你通过main方法启动一个简单的java程序时,你不在程序里调用new Thread().start(),但是JVM仍然会创建一个main thread 去运行main方法. main线程

【Java】深入理解ThreadLocal

一.前言 要理解ThreadLocal,首先必须理解线程安全.线程可以看做是一个具有一定独立功能的处理过程,它是比进程更细度的单位.当程序以单线程运行的时候,我们不需要考虑线程安全.然而当一个进程中包含多个线程的时候,就需要考虑线程安全问题,因为此时线程可能会同时操作同一个资源,当两个或者两个以上线程同时操作一个资源的时候,就会造成冲突.不一致等问题,即线程不安全. 解决线程安全问题,本质上就是解决资源共享问题,一般有以下手段: 1)可重入(不依赖环境):2)互斥(同一时间段只允许一个线程使用)

Java 并发:深入理解 ThreadLocal

摘要: ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证变量在不同线程间的隔离性,以方便每个线程处理自己的状态.进一步地,本文以ThreadLocal类的源码为切入点,深入分析了ThreadLocal类的作用原理,并给出应用场景和一般使用步骤. 一. 对 ThreadLocal 的理解 1). ThreadLocal 概述 ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制,可以为每一个使用该变量的线程都提供一个

理解ThreadLocal类

1 ThreadLocal是什么 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象.当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本.所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应

[JavaEE]理解ThreadLocal

转http://www.iteye.com/topic/103804 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什

理解ThreadLocal(二)

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本.通过ThreadLocal.set()将这个新创建

理解ThreadLocal(一)

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”.其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些. 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变

简单理解ThreadLocal原理和适用场景

https://blog.csdn.net/qq_36632687/article/details/79551828?utm_source=blogkpcl2 参考文章: 正确理解ThreadLocal 一个故事讲明白 ThreadLocal 原文地址:https://www.cnblogs.com/zt007/p/9797762.html

彻底理解ThreadLocal

学习一个东西首先要知道为什么要引入它,就是我们能用它来干什么.所以我们先来看看ThreadLocal对我们到底有什么用,然后再来看看它的实现原理. ThreadLocal如果单纯从名字上来看像是“本地线程"这么个意思,只能说这个名字起的确实不太好,很容易让人产生误解,ThreadLocalVariable(线程本地变量)应该是个更好的名字.我们先看一下官方对ThreadLocal的描述: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通