Java多线程技术二:线程间通信——ThreadLocal的使用

1 概述

        变量值的共享可以使用public static 的声明方式,所有的线程都是用同一个public static变量,那如果想实现每一个线程都有自己的变量该如何解决呢?JDK提供的ThreadLocal就派上用场了。

        ThreadLocal类主要的作用就是将数据放入当前线程对象中的Map里,这个Map类是Thread类的实例变量。ThreadLocal类自己不管理也不存储任何数据,它只是数据和Map之间的中介和桥梁,通过ThreadLocal将数据放入Map中,执行流程如下:

数据——>ThreadLocal——>currentThread()——>Map

        执行后每个线程中的Map就存储自己的数据,Map中的key存储的是ThreadLocal对象,value就是存储的值,说明ThreadLocal和值之间是一对一的关系,一个ThreadLocal对象只能关联一个值。每个线程中Map的值只对向前线程可见,其他线程不可以访问当前线程对象中Map的值。内存结构如下:

2 get()方法与null

        如果从未在Thread中的Map存储 ThreadLocal对象对应的值,则get()方法返回null。

public class Run1 {
    public static ThreadLocal t1 = new ThreadLocal();

    public static void main(String[] args) {
        if(t1.get() == null){
            System.out.println("从未放过值");
            t1.set("第一次放的值");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }
}

        ThreadLocal类解决的是变量在不同线程中的隔离性,也就是不同线程拥有自己的值,不同线程中的值是可以通过ThreadLocal类进行保存的。

3 ThreadLocal类存取数据流程分析

        运行测试程序:

public class Test {
    public static void main(String[] args) {
        ThreadLocal local = new ThreadLocal();
        local.set("value");
        System.out.println(local.get());
    }
}

        从JDK源码角度来分析一下ThreadLocal类执行存取操作的流程。

        首先看一下数据如何存入到ThreadLocal中的。

        (1)执行ThreadLocal.set("value")代码时,ThreadLocal代码如下:

public void set(T value) {
        //对象t就是main线程
        Thread t = Thread.currentThread();
        //从main线程中获取ThreadLocalMap 
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //如果map不等于null,则set操作
            map.set(this, value);
        } else {
            //如果map等于null,则先执行创建,在执行set
            createMap(t, value);
        }
    }

        (2)ThreadLocalMap map = getMap(t); 源码如下:

ThreadLocalMap getMap(Thread t) {
        //参数t就是前面传入的main线程
        return t.threadLocals;//返回main线程中threadLocals变量对应的ThreadLocalMap对象
    }

        对象threadLocals数据类型就是ThreadLocal.ThreadLocalMap,变量threadLocals是Thread类中的实例变量。

        (3)取得Thread中的ThreadLocal.ThreadLocalMap后,根据map对象值是不是null来决定是否对其执行set或create and set操作。

        (4)createMap()方法的功能是创建一个新的ThreadLocalMap,并在这个新的ThreadLocalMap里存储数据,ThreadLocalMap中的key就是当前ThreadLocal对象,值就是传入的value,createMap()方法的源码如下:

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

        在实例化ThreadLocalMap的时候,向构造方法传入thi和firstValue,其中,this就是当前ThreadLocal对象,firstValue就是调用ThreadLocal对象时set()方法传入的参数值。

new ThreadLocalMap(this, firstValue)的源码是:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        在源码中可以发现,将ThreadLocal对象与firstValue封装进Entry对象中,并放入table[]数组。

        再看一下get()的执行流程。

        (1)当执行 local.get() 代码时,ThreadLocal.get()源码如下:

    public T get() {
        Thread t = Thread.currentThread();//t 就是main线程
        ThreadLocalMap map = getMap(t);//从main线程中获取ThreadLocalMap 
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//如果map不等于null,获取Entry对象
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

        总结:上面的几个步骤就是set和get的执行流程,比较麻烦。为什么不能直接向Thread类中的ThreadLocalMap对象存取数据呢?这是无法实现的,原因参考下面代码:

ThreadLocal.ThreadLocalMap threadLocals = null;

        变量 threadLocals 默认是包级访问,所以不能从外部直接访问该变量,也没有对应的get和set方法,只有用同一个包中的类可以访问threadLocals变量,而ThreadLocal和Thread恰好在同一个包中(都在java.lang包下)。

4 验证线程变量的隔离性

        本节将实现通过使用ThreadLocal在每个线程中存储自己的私有数据。

public class Tools {
    public static ThreadLocal t1 = new ThreadLocal();
}
public class MyThreadA extends Thread{
    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                Tools.t1.set("A: " +(i+1) );
                System.out.println("A get:" + Tools.t1.get());
                int sleepValue = (int)(Math.random() * 10000);
                Thread.sleep(sleepValue);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class MyThreadB extends Thread{
    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                Tools.t1.set("B: " +(i+1) );
                System.out.println("B get:" + Tools.t1.get());
                int sleepValue = (int)(Math.random() * 10000);
                Thread.sleep(sleepValue);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadA a = new MyThreadA();
        MyThreadB b = new MyThreadB();
        a.start();
        b.start();
        for (int i = 0; i < 10; i++) {
            Tools.t1.set("main: " +(i+1) );
            System.out.println("main get:" + Tools.t1.get());
            int sleepValue = (int)(Math.random() * 10000);
            Thread.sleep(sleepValue);
        }

    }
}

        控制台输出的结果表示通过ThreadLocal向每个线程存储自己的私有数据,虽然3个线程都向t1存放数据,但是每个线程仅能取出自己的数据,不能取出其他线程存放的数据 。

5 解决get()返回null的问题

        新建ThreadLocalExt.java,继承ThreadLocal类,并覆盖 initialValue() 方法

public class ThreadLocalExt extends ThreadLocal{
    @Override
    protected Object initialValue(){
        return "我是默认值,第一次get不再为null";
    }
}

         覆盖initialValue()方法具有初始值,因为ThreadLocal.java中的initialValue方法默认返回值就是null,所以要在子类中重写。源码如下:

protected T initialValue() {
        return null;
    }
public class Run1 {
    public static ThreadLocalExt t1 = new ThreadLocalExt();

    public static void main(String[] args) {
        if(t1.get() == null){
            System.out.println("没有存放过值");
            t1.set("第一次存放值");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }

6 验证重写initialValue()方法的隔离性

public class Tools {
    public  static ThreadLocalExt t1 = new ThreadLocalExt();
}

public class ThreadLocalExt extends ThreadLocal{
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }
}
public class ThreadA extends Thread{
    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在线程ThreadA中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}
public class Run1 {
    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在Main线程中取值 = " + Tools.t1.get());
            }
            Thread.sleep(2000);
            ThreadA threadA = new ThreadA();
            threadA.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

7 使用remove()方法的必要性

        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;
            }
        }

         弱引用的特点是,只要垃圾回收器扫描时发现弱引用的对象,就不管内存是否足够,都会回收弱引用的对象。也就是只要执行gc操作,ThreadLocal对象就会立即销毁,代表key的值ThreadLocal对象会随着gc操作而销毁,释放内存空间,但value值却不会随着gc操作而销毁,这会出现内存溢出。如果对象数量过多,对于ThreadLocalMap类中不用的数据使用ThreadLocal类的remove方法进行清除,实现业务对象的垃圾回收,释放内存。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

geminigoth

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值