zoukankan      html  css  js  c++  java
  • ThreadLocal分析

    我们再介绍一个在多线程环境中经常使用的类ThreadLocal,它是java为解决多线程程序的并发问题提供了一种新的方向,使用这个ThreadLocal类可以帮助开发者很简单地编写出简洁的程序,并且是线程安全的。ThreadLocal很容易让人误解,认为是一个“本地线程”,其实ThreadLocal并不是一个Thread,而是Thread的一个局部变量。

    当使用ThreadLocal变量时,ThreadLocal为每个使用这个变量的线程提供不同的变量副本,所以每一个线程改变的只是自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量。我们先来了解一下ThreadLocal的接口方法

    ThreadLocal类提供的接口方法比较简单,很容易使用:

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

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

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

      protected Object initialValue()   返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的,ThreadLocal中的缺省实现直接返回一个null。

    下面举一个列子来说明一下他的用法。

     

    public class TestThreadLocal {

    static class myRunnable implements Runnable {
    int index;
    public myRunnable(int i){
    index =i;
    }
    public void run() {
    myThreadLocal.set("Thread:"+ index);
    System.out.println(myThreadLocal.get());

    }
    }

    public static ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();

    public static void main(String[] args){
    Thread thread1 = new Thread(new myRunnable(1));
    Thread thread2 = new Thread(new myRunnable(2));
    thread1.start();
    thread2.start();
    System.out.println(myThreadLocal.get());
    }
    }

    执行的结果如下:

    Thread:1
    Thread:2
    null

    thread 1 返回Thread:1的结果,thread2返回Thread:2,最后一个null是main线程的执行结果,因为 main线程没有设置自己的局部变量。

    ThreadLocal的实现原理

    在介绍ThreadLocal变量之前,我们先来看一个Thread的内部变量:

      ThreadLocal.ThreadLocalMap threadLocals = null;

    这个变量就是线程用来存储自己的ThreadLocal变量的空间,他的类型 ThreadLocal.ThreadLocalMap,作用相当于一个hashmap,对这种数据类型的操作是不需要加锁的,因为只有一个线程(也就是线程自己)才可能操作到自己的变量。

    我们先看ThreadLocal源代码是来解析两个比较重要的函数:set和get

        public void set(T value) {

            Thread t = Thread.currentThread();

            ThreadLocalMap map = getMap(t);//拿到本地线程map

            if (map != null)

                map.set(this, value);//map存在时,添加value

            else

                createMap(t, value);//map不存在时,创建map并且添加value

    }

        public T get() {

            Thread t = Thread.currentThread();

            ThreadLocalMap map = getMap(t); //拿到本地线程map

            if (map != null) {//map存在时,或者key

                ThreadLocalMap.Entry e = map.getEntry(this);

                if (e != null)

                    return (T)e.value;

            }

            return setInitialValue();

        }

        ThreadLocalMap getMap(Thread t) {

            return t.threadLocals;

        }

        void createMap(Thread t, T firstValue) {

            t.threadLocals = new ThreadLocalMap(this, firstValue);

        }

    从代码上看,ThreadLocal函数都是先获得当前线程的引用,然后得到线程局部变量,最后以ThreadLocal作为key,设置或者获取value,线程本地变量是类型为ThreadLocalMap的变量,ThreadLocalMap相当于一种哈希表,有兴趣的可以看一下。整个过程没有任何同步操作,因此性能很高。

    ThreadLocal与其它同步机制的比较 

    ThreadLocal和线程同步机制都是为了解决多线程访问变量冲突的问题,同步机制是通过对象的锁机制保证同一时间只有一个线程访问变量,此时该变量是多个线程共享的,使用同步机制要求用户分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等复杂的问题,程序设计和编写难度相对比较大。而ThreadLocal则从另一个角度来解决多线程的并发访问,它会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突,因为每一个线程都拥有自己的变量副本,它只操作自己的变量副本,从而也就没有必要对该变量进行同步了。

    当然ThreadLocal并不能替代同步机制,两者面向的问题不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源,这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更加易读、简洁。

    InheritableThreadLocal

    通常线程还需要维护另外一个变量 inheritableThreadLocals

      ThreadLocal.ThreadLocalMap inheritableThreadLocals = null

    当创建一个新的线程时,线程会从父线程哪里复制一份数据。也就是是说,子线程会享有父线程的数据,下面的代码摘自Thread.init函数,子线程创建时,将会调用。

            if (parent.inheritableThreadLocals != null)

                this.inheritableThreadLocals =

                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

    请看它的getMap函数,显然操作的是inheritableThreadLocals变量

    ThreadLocalMap getMap(Thread t) {

           return t.inheritableThreadLocals;

        }

    这时所有的操作都是针对Thread的inheritableThreadLocals,这种操作也是线程安全的。

    我们来举一个例子来说明InheritableThreadLocal的说法

    public class TestInheritableThreadLocal {

    static class myRunnable implements Runnable {
    int index;
    public myRunnable(int i){
    index =i;
    }
    public void run() {
    //
    System.out.println("thread " + index + "-" + myThreadLocal.get());
    myThreadLocal.set("Thread:"+ index);
    System.out.println("thread " + index + "-" + myThreadLocal.get());

    }
    }

    public static InheritableThreadLocal<String> myThreadLocal = new InheritableThreadLocal<String>();

    public static void main(String[] args){

    myThreadLocal.set("parent main thread");
    Thread thread1 = new Thread(new myRunnable(1));
    Thread thread2 = new Thread(new myRunnable(2));
    thread1.start();
    thread2.start();
    try {
    thread1.join();
    thread2.join();
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }

    System.out.println("main thead -" + myThreadLocal.get());
    }
    }

    结果如下:

    thread 1-parent main thread
    thread 2-parent main thread
    thread 2-Thread:2
    thread 1-Thread:1
    main thead -parent main thread

    线程1和线程2会共享main线程的数据,子线程也可以修改自己的副本,修改后,线程1得到的结果Thread:1,线程2得到结果Thread:2

    而父线程(main)的变量时不会噶边的,因为子线程的修改只作用于自己的变量空间,不会作用到父线程。

    概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而不受影响。

  • 相关阅读:
    使用Vue快速开发单页应用
    轻松入门React和Webpack
    使用Redux管理你的React应用
    深入理解 react-router 路由系统
    webpack学习之路
    webpack编译流程漫谈
    HTML5无刷新修改Url,history pushState/replaceState
    gulp的流与执行顺序
    RequireJS对文件合并与压缩实现方法
    RequireJS模块化后JS压缩合并
  • 原文地址:https://www.cnblogs.com/easycloud/p/3729894.html
Copyright © 2011-2022 走看看