(Java基础篇)五、Java中的ThreadLocal类

多线程

  多线程编程中往往可能需要专门为一个线程维护独有的数据,例如一场考试中,有100个人在做题,每个人虽然都是同一个试卷,但是作答的结果肯定有不同,那么就需要有一个线程隔离的变量保存每个人的答案。首先看Thread源码。

Thread

  Thread的源码中有一个成员变量是ThreadLocalMap类型,这个类型是ThreadLocal类型的内部静态类。我们先通过名字猜测一下是干什么的,首先ThreadLocal就是"线程+本地"两个词组合在一起,那么很容易想到就是线程自身属性的一些东西,加个s就是一堆线程自身属性的东西,换句话来说就是当前线专属自己的变量们,为什么加个“们”,往后看就知道了。
Thread类

ThreadLocal

  这个就是我们的核心类,当我们需要保存当前线程专属的变量时,就申请一个这个东西。举个例子,有个人来到了公司,进门的时候通过人脸识别身份认证,挂上工牌就知道他是A部门经理,然后他去B部门的档案室,那里的门卫看看他的牌子(没有人脸识别身份认证)说:你是A部门的,不能进B部门档案室。这里,这个人就是一个线程,进门人脸认证就是鉴权,然后认证成功后,挂牌的过程就是将认证信息放到这个线程的threadLocals中(这个通过ThreadLocal的set方法实现),等到要进入B部门的时候,查看牌子就是ThreadLocal.get就不用通过人脸认证了。

  现在我们重点关注ThreadLocal的get和set方法,至于Thread中的threadLocals变量可以暂时理解为这个人身上除了这个牌子,还有手机、电脑等等其他专属于他的物品。

  在关注set方法之前,我们得先new一个出来,不new出来怎么set嘞。new的话我们就用下面的

class Person extends Thread{
	// job意思就是这个人的职位
	private ThreadLocal<String> job= new ThreadLocal<>();
}
// ThreadLocal源码
public void set(T value) {
	// 获取当前线程对象
    Thread t = Thread.currentThread();
    // 根据当前线程对象,取得当前线程对象的threadLocals(这是个map)
    // getMap(t) 就是 t.threadLocals;
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    	// 给map中添加元素
        map.set(this, value);
    } else {
    	// 如果是空的map就重新创建一个
        createMap(t, value);
    }
}
void createMap(Thread t, T firstValue) {
	// 用构造函数的方式创建新的map
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocalMap源码
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);
}

  首先关注set方法,他做的第一步就是获取当前的线程对象,因为存储当前线程独有的数据肯定得先获取线程,然后将数据存到threadLocals里面。因此首先做的就是get到当前线程对象的threadLocals成员变量,如果是空的,那就new一个并且把值作为第一个存进去,如果不是空的就添加一个。这个threadLocals实际上是一个map,为什么是map呢,因为当前的线程可以拥有好多个独有的变量,以key-value的形式存储在当前线程对象的threadLocals成员变量中。其中这个key呢就是this,这个this是ThreadLocal对象的this,也就是上面那个job的this(它的hashCode),value是我们想要的值,这样以后想要的时候,使用job.get(),在get方法里先获取到当前线程的threadLocals,然后以job自身(它的hashCode)作为this,去map中取值。

  为什么是hashCode呢,我们去map.set方法中看一看就知道了,其实这个key就是hashCode取了一下余。

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    // 剩余省略
}

  下面我们再看一下get方法,理解了set方法之后get方法就更简单了。

public T get() {
    Thread t = Thread.currentThread();
    // 获取当前线程的threadLocals
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    	// 把this(hashCode)作为key取值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

  最后我们用一个例子来说明这个过程。通过这个例子就可以明白为什么需要用map来存数据,而且还要用ThreadLocal的hashCode做key,这都是因为一个Thread可以有多个专属变量。例如我们这个人可以有自己的电脑,也可以有自己的职位。将数据存在当前线程的threadLocals变量中是为了隔离,用map是为了job在get的时候不会错误的取到laptop。

class Person extends Thread {

    public ThreadLocal<String> job = new ThreadLocal<>();
    ThreadLocal<String> laptop = new ThreadLocal<>();

    @Override
    // 从run开始,这个人从家里出发去上班
    public void run() {
        // 打包好电脑
        laptop.set("昨天买的笔记本");
        // 到公司门口人脸识别,用sleep模拟
        try {
            sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 识别成功,他是A部门经理
        job.set("A部门经理");

        // 到工位取出电脑
        System.out.println("今天带的笔记本电脑是:" + laptop.get());
        // 去B部门档案室
        System.out.println("你的职位是:" + job.get() + ", 不能进入");

    }

}

public class TestThreadLocal {
    public static void main(String[] args) {
        Person person = new Person();
        // 这里这个人的职务是B部门经理,为什么输出的是A部门经理?
        person.job.set("B部门经理");

        person.start();
    }
}

  在创建person的时候给他的职位是B部门经理,然后我们启动线程,发现输出的是A部门经理,这是因为,在创建person的时候是一个线程,而启动这个线程又开了一个新的,两个线程不在一起,因此不一致。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值