关于ThreadLocal的一些认识

在前面讲关于Handler的Looper的时候,我们讲到过Looper的获取和设置,是通过ThreadLocal来操作的,如下:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

在准备Looper的时候,

    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

在获取Looper的时候,

   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

那么ThreadLocal的作用到底什么呢?为什么会有ThreadLocal的出现呢?

首先,我们先来看下面一个例子吧,如下:

public class HelperTesting {

    private String text = "empty";

    private static HelperTesting helperTesting = new HelperTesting();

    private void printText() {
        System.out.println(text);
    }

    private void changeText(String str) {
        text = str;
    }

    private void printWithLine(String str){
        System.out.println("##################### " + str +" #############################");
    }

    public static void main(String[] args) {

        helperTesting.printWithLine(Thread.currentThread().getName() + " start");
        helperTesting.printText();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                helperTesting.printWithLine(Thread.currentThread().getName() + " start");
                helperTesting.printText();
                helperTesting.changeText("thread1");
                helperTesting.printText();
                helperTesting.printWithLine(Thread.currentThread().getName() + " end");
            }
        });

        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        helperTesting.printText();
        helperTesting.printWithLine(Thread.currentThread().getName() + " end");
    }
}

在这个小Demo里面,我们做了几件事情:

1)创建了一个HelperString对象,其有一个 text 字段,初始化其值为 “empty”。

2)创建了一个新的线程,在线程中改变helperString对象的 text 的值为"thread1",运行线程,并打印其值。

3)最后我们在原来的线程中打印出text的值。

运行结果如下:

##################### main start #############################
empty
##################### Thread-0 start #############################
empty
thread1
##################### Thread-0 end #############################
thread1
##################### main end #############################

我们可以看到,在主线程中的 text 值被改变了。

在多线程的编程过程当中,因为thread共享同一段进程空间,所以A线程的运行是完全有可能在不经意间就改变了某个值,而当B线程也去拿这个值的时候,可能就已经不是其想要获取的数值了。

于是有人就想,能不能让线程也拥有其私有的一些空间呢,让存放在这段空间里面的值并不会被其他线程改变,而这就是ThreadLocal的出现。

注意,其出现只是为了保证线程数据的私有性,并不是为了解决线程间的同步问题,我估计这也是其为什么叫Thread Local 的原因。

接下来,我们再通过一个Demo来看看,怎么使用ThreadLocal,如下:

public class HelperTestingSec {

    private ThreadLocal<String> sThreadLocal = new ThreadLocal<String>();

    private static HelperTestingSec helperTesting = new HelperTestingSec();

    private HelperTestingSec(){
        sThreadLocal.set("empty");
    }

    private void printText() {
        System.out.println(sThreadLocal.get());
    }

    private void changeText(String str) {
       sThreadLocal.set(str);
    }

    private void printWithLine(String str){
        System.out.println("##################### " + str +" #############################");
    }
    public static void main(String[] args) {

        helperTesting.printWithLine(Thread.currentThread().getName() + " start");
        helperTesting.printText();      

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                helperTesting.printWithLine(Thread.currentThread().getName() + " start");
                helperTesting.printText();
                helperTesting.changeText("thread1");
                helperTesting.printText();
                helperTesting.printWithLine(Thread.currentThread().getName() + " end");
            }
        });

        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        helperTesting.printText();
        helperTesting.printWithLine(Thread.currentThread().getName() + " end");
    }
}

这个Demo中做的事情,其实跟上一个Demo类似,唯一不同的是,在这个Demo中,我们利用一个ThreadLocal对象来存放这个String的值,运行结果如下:

##################### main start #############################
empty
##################### Thread-0 start #############################
null
thread1
##################### Thread-0 end #############################
empty
##################### main end #############################

从上面的结果,我们可以看到,同一个helperTestingSec对象,在 main线程中,其 sThreadLocal中存放的对象一直是empty。

而在新建的线程当中呢,当在调用changeText进行设值之前,其value是null的,也就是说在新线程中,虽然是同一个对象,但是其值是不一样的,即使我们在新线程中改变了其值,也不会影响其在Main线程中的值。

可见,通过 ThreadLocal,我们就做到了线程间的数据独立了。

那么,ThreadLocal的实现原理是什么,为什么其能做到这个事情呢,我们还是想了解一下其内部的构造的,看一下能不能学到点什么东西,对吧。

按照顺序,一般情况下,我们总是先放东西进去,再去拿东西出来的,所以我们先看一下ThreadLocal 的 set 方法。

有一点要注意的是,这里讲解的ThreadLocal的实现是Android里面的实现,JDK中的实现方式有点不一样,但是原理是一样的。

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

在Demo2中,我们调用sThreadLocal的 set方法的时候,我们就进入了上面的逻辑。

可以看到,第一步,就是先利用Thread.currentThread拿到当前的线程,再根据这个线程去获取一个叫做Values的对象。再看一下values函数,我们可看到其是从哪里去获取这个Values对象的,如下:

   /**
     * Creates Values instance for this thread and variable type.
     */
    Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }

    /**
     * Gets Values instance for this thread and variable type.
     */
    Values values(Thread current) {
        return current.localValues;
    }

可以看到,其是从current这个线程里去获取Values对象的,而如果不存在这个对象,其就会调用initializeValues方法,为线程的localValues创建一个新的Values对象。

最近再调用values的put方法,以当前ThreadLocal对象为key值,将我们的valule给存放到这个Values对象中,我们会马上意识到,Values实现的应该是一个类似Map的键值对的数据结构。

从这里,我们就可以意识到:

1)在每个线程中都存在一个Values对象。

2)无论我们在哪个线程中调用ThreadLocal对象,都会先去当前线程中取得一个Values对象(如果不存在,就创建一个)再对这个Values对象进行键值操作,比如ThreadLocal的Get方法,如下:

    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

3)一个ThreadLocal对象其实是可以给所有线程共享的,因为其只是作为一个入口和一个WeakRefreence的Key值,真正的数据都是存在Values中,而这个Values对象是线程独有,且在当前线程中是唯一的。

所以这也是为什么在Looper类中,sThreadLocal对象是一个静态的对象就行了。

而Values的内部实现,其实是用一个Object数组来做为数据结构的,偶数位(0,2,4....)等作为Key值,其存放是指向ThreadLocal对象本身的一个WeakReference, 奇数位(1,3,5....)等作为的Value值,存放的是我们设置的值,从上面 get方法中, 我们也可以略窥一二。

在这里,推荐两边文章,相信对大家了解ThreadLocal有一定的帮助。

When and how to use a ThreadLocalHow is ThreadLocal implemented?

时间: 2024-08-29 20:34:39

关于ThreadLocal的一些认识的相关文章

Java中ThreadLocal的深入理解

官方对ThreadLocal的描述: "该类提供了线程局部(thread-local)变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本.ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程(例如,用户ID或事物ID)相关联." <Thinking in Java>中的描述: 防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享.线程本地

java 之ThreadLocal

通过 ThreadLocal 能数据保存在一个线程中,而且不需要 lock 同步.理论上 ThreadLocal 可 以让一个变量在每个线程都有一个副本. ThreadLocal 常用来屏蔽线程的私有变量,例如"并 发事务"或者其他的资源.而且,它还被用来维护每个线程的计数器,统计,或者 ID 生成 器. 由ThreadLocal常用的get方法定义看: public T get() { Thread t = Thread.currentThread(); ThreadLocalMap

图解ThreadLocal

ThreadLocal ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本. 从线程的角度看,目标变量就象是线程的本地变量,这也是类名中"Local"所要表达的意思. 那么ThreadLocal是如何做到的呢?上图. 图没看懂?那我们来看一下源

Java中线程封闭之ThreadLocal

在访问共享数据时通常使用同步.若不使用同步则可以将对象封闭在一个线程中达到线程安全的目的,该方法称为线程封闭(Thread Confinement).其中实现线程封闭中规范的方法是使用ThreadLocal类.线程封闭技术一种常用的使用场景是在JDBC Connection对象. public class ConnectionHelper{private final static String URL = "";private final static ThreadLocal<C

ThreadLocal

为了防止内存泄露,一旦threadlocal完成任务,最好调用remove() 详见:ThreadLocal explained

并发编程(四):ThreadLocal从源码分析总结到内存泄漏

一.目录 1.ThreadLocal是什么?有什么用? 2.ThreadLocal源码简要总结? 3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLocal是什么?有什么用? 引入话题:在并发条件下,如何正确获得共享数据?举例:假设有多个用户需要获取用户信息,一个线程对应一个用户.在mybatis中,session用于操作数据库,那么设置.获取操作分别是session.set().session.get(),如何保证每个线程都能正确操作达到想要的结果? /** * 回顾sync

从源码理解 ThreadLocal()

每一反应,使用Thread中定义一个成员变量来解决 线程局部变量问题: 为什么要使用ThreadLocal(),有什么好处: 单例的完整性: 解决了线程上下文中的变量传递问题,达到线程安全目的: ThreadLocal和synchronized的区别? ThreadLocal使用场景 待完成...

理解Java ThreadLocal

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

我对ThreadLocal的理解

声明:小弟菜狗一个.对ThreadLocal的描写叙述和理解难免有所偏差 近期由于须要深入的了解android的handler消息机制而去查看了Looper的源代码.众所周知在主线程中是不须要在程序猿在代码新建一个Looper对象的,由于在主线程创建时它就被创建出来了.所以就好奇它是怎么被创建出来的然后发现它跟ThreadLocal 有关于是便查看了该类的一些资料,但还是不太理解.于是便尝试自己去看一下源代码,然后就有了对ThreadLocal一个又一次的认识.先贴出Looper的代码: pri

ThreadLocal实现原理

一.ThreadLocal介绍 这是一个线程的局部变量.也就是说,只有当前线程可以访问.既然是只有当前线程可以访问的数据,自然是线程安全的. 为每一个线程分配不同的对象,需要在应用层面保证.ThreadLocal只是起到了简单的容器作用. 二.实现原理 1. 我们需要关注的是ThreadLocal的set()方法和get()方法. set()方法 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMa