ThreadLocal源码解析

无论是在面试还是在平时得web开发中,我们经常会遇到线程安全问题。在不同线程去操作共享数据得时候(mysql、redis、jvm内存中的数据),必定会涉及到数据安全问题。mysql有自己的行锁来确保安全性。redis是基于内存的单线程数据库,所以不会存在线程安全问题。在jvm中的数据如何保证线程操作的安全性呢?

1、volatile + cas保证线程安全

volatile规定的缓存一致性与有序性,在大部分形况下已经可以保证线程安全了。
而cas更不必多说,在java并发包下的所有的类的实现都是基于抽象队列同步器去实现,而抽象队列同步器就是用cas去做的。

对于这一块可以参考我前面分析的读写锁的分析哪一块

2、synchronize锁

保证线程安全最简单最粗暴的方式就是加锁了。但是加锁也就导致系统并发度降低~~

3、java.util.concurrent

这个包里面提供了一系列的线程安全的类。我们可以使用这些去替代不安全的类。

ConcurrentHashMap map
ReentrantLock 锁—共享锁与排它锁
CompletableFuture 线程编排的类
CopyOnWriteArrayList list
CopyOnWriteArraySet set
CountDownLatch 闭锁
DelayQueue 延时队列
Executors 线程的工具类
Semaphore 信号量
atomic包下的一些类

4、ThreadLocal

threadlocal同样也可以保证线程安全,因为他是将数据保存在了本线程变量中。避免了其他线程操作这个变量。那么就不存在跟其他线程的数据共享问题。

我们就仔细分析下ThreadLocal的实现原理

ThreadLocal的数据存储

public void set(T value) {
		//获取当前线程
        Thread t = Thread.currentThread();
        //获取map  这个map是Thread类中的一个变量  threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

首先看下这个createMap是怎么创建的

void createMap(Thread t, T firstValue) {
		//初始化了一个Map,并且本线程持有这个map的引用
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

//map的实例化      firstKey:ThreadLocal  firstValue  存放的值
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
			//初始化一个entry数组  类似与hashMap的数组结点
            table = new Entry[INITIAL_CAPACITY];
            //计算threadLocal所在的下标
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //实例化一个entry结点,放在数据中
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            //设置entry的扩容阙值
            setThreshold(INITIAL_CAPACITY);
        }

创建好了map那么下一次就会直接使用

再来看下如果 有map的话,值是怎么添加保存的

private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
			
			//上面提到了get()会使用快速路径? 什么是快速路径? 后面在分析
            Entry[] tab = table;
            int len = tab.length;
            //计算索引
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 //获取threadLocal  这里是一个弱引用 ---> 方便gc
                ThreadLocal<?> k = e.get();
				//这个位置的threadLocal就是我们正在设置的threadLocal
                if (k == key) { 
                	//直接将结点的值替换成新的
                    e.value = value;
                    return;
                }
				//如果k为null,entry结点不为null,但是它持有的threadLocal却为null?
                if (k == null) {
                	//处理~~~ 
                	//简单来说就是再一次判断数组中是否有entry存储的threadLocal是我们处理的这个
                	//没有新增一个entry放在数组中
                	//最后判断i索引之后(之前)的结点不为空,并且结点持有的threadLocal为null  那么就删除这个结点
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//创建一个新的结点  到了这里说明这个threadLocal肯定没有在本线程中存储过值
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //判断i索引之后的结点不为空,并且结点持有的threadLocal为null  那么就删除这个结点
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

上面为什么会有entry结点不为空,但是entry却没有对应的threadLocal? 人为将threadLocal设置为null?

最后看一下get取值

public T get() {
        Thread t = Thread.currentThread();
        //获取线程的map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//计算threadLocal的索引,拿到对应的entry结点
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //获取值
                T result = (T)e.value;
                return result;
            }
        }
        //没有map初始化一个map并且设置为threadLocal对应的entry结点,值设置为null
        return setInitialValue();
    }

再来看一下threadLocal、map、thread、entry之间的关系
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值