ThreadLocal底层源码

8 篇文章 0 订阅
1 篇文章 0 订阅

SimpleDateFormat非线程安全问题

public class ThreadLocalTest {
    //非线程安全的,因为SimpleDateFormat当中的 NumberFormat 对象是全局变量,而sdf又是static共享的,导致非线程安全问题
    private static final SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    public static Date parse(String strDate) throws ParseException {
        return sdf.parse(strDate);
    }

    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(10);
        for (int i = 0; i < 20; i++) {
            executorService.execute(()->{
                try {
                    System.out.println(parse("2021-05-30 20:12:20"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

ThreadLocal解决线程隔离

public class ThreadLocalDemo {
    private static ThreadLocal<DateFormat> dateFormatThreadLocal=new ThreadLocal<>();
    
    private static DateFormat getDateFormat(){  // ThreadLocal对每个线程都做到了隔离
        DateFormat dateFormat=dateFormatThreadLocal.get(); //从当前线程的范围内获得一个DateFormat
        if(dateFormat==null){
            dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //Thread.currentThread();
            dateFormatThreadLocal.set(dateFormat); //要在当前线程的范围内设置一个simpleDateFormat对象.
        }
        return dateFormat;
    }
    public static Date parse(String strDate) throws ParseException {
        return getDateFormat().parse(strDate);
    }

    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(10);
        for (int i = 0; i < 20; i++) {
            executorService.execute(()->{
                try {
                    System.out.println(parse("2021-05-30 20:12:20"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            });

        }
    }
}

ThreadLocal的原理

  1. set()
public void set(T value) {
    Thread t = Thread.currentThread();    // 获取当前线程
    ThreadLocalMap map = getMap(t);    // 获取本地线程ThreadLocalMap对象
    if (map != null)
        map.set(this, value);    
    else
        createMap(t, value);    // 本地线程ThreadLocalMap对象着创建新得ThreadLocalMap对象
}

// 创建ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 新建数组并且存入当前ThreadLocalMap对象得值
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];    // 创建一个默认长度为16得数组
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);    // 计算当前ThreadLocalkey得code值%数组得长度-1 算出存放得下标
    table[i] = new Entry(firstKey, firstValue);
    size = 1;    
    setThreshold(INITIAL_CAPACITY);    // 设置下一次需要扩容数量
}
// 设置下一次需要扩容数量
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;    // 获取本地线程ThreadLocalMap对象
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);    // 计算当前ThreadLocalKey存放位置
    // 线性探索
    for (Entry e = tab[i];    //查看图一解说
         e != null;    // 判断当前i索在得位置是否有值,如果有着结束
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();    // 获取当前i所在得位置值

        if (k == key) {    // 判断是否等于ThreadLocalKey,存在着直接替换
            e.value = value;
            return;
        }

        if (k == null) {    // 判断当前key是空值,则replaceStaleEntry替换空的值
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);    //清除完毕未查询到当前ThreadLocal的key的值,并且索引下标为空
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)    //清除数据,并且判断当前ThreadLocalMap大小是否需要扩容
        rehash();
}
// 清理下标为空的数据
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;
    int slotToExpunge = staleSlot;
    //在当前运行中检查之前的陈旧条目。
    //我们一次清洗整个运行,以避免连续
    //由于垃圾回收器释放,增量重散列
    // up成组引用(例如,每当收集器运行时)。
    for (int i = prevIndex(staleSlot, len);    // 根据索引11 向左查询到第一个为null的索引结束    查看图二
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        if (e.get() == null)    // 如果中途有索引下标key的value值为空索引位置标记给slotToExpunge
            slotToExpunge = i;

    for (int i = nextIndex(staleSlot, len);    // 继续向右查询判断是否空
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();

//如果我们找到了key,那么我们需要交换它
//保持哈希表的顺序。
//新过时的插槽,或任何其他过时的插槽
//被发送到expungeStaleEntry
//删除或重新散列运行中的所有其他条目。
        if (k == key) {    
        // 继续向右查询,如果查询到k == key,相互替换
            e.value = value;

            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            // 如果过期索引存在,则从之前的索引开始删除
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);    // 清理下标key的value值为null的数据
            return;
        }

        // If we didn't find stale entry on backward scan, the
        // first stale entry seen while scanning for key is the
        // first still present in the run.
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    // If key not found, put new entry in stale slot
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // If there are any other stale entries in run, expunge them
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
// 清除value为null的数据
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 清除当前索引下标的值,赋值为null
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);    // 循环清除下标,如果为空着结束
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {    // 清除当前索引下标的值,赋值为null
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);    // 查看图三
            if (h != i) {    // 判断当前key的下标是否属于他自己的下标位置
                tab[i] = null;    // 不属于赋值为空,并且继续往下找到为空的位置存放

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}
private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);    // (n=n/2)!=0    
    return removed;
}
  • 图一
    在这里插入图片描述

  • 图二
    在这里插入图片描述

  • 图三
    在这里插入图片描述

  1. get()
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);    // 获取ThreadLocad对应的值
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();    // ThreadLocadMap为空着着创建,并且赋值当前ThreadLocad值为null
}
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)    // 如果key计算的下标是等于当前key的值直接返回
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}
// 当前下标位置给其他key占用着继续往下查询
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)    // 当前下标value值为null清理数据
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);    // 往右查询数据
        e = tab[i];
    }
    return null;    // 未查询到为空
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值