ThreadLocal源码分析

前面写了一些关于JUC包下的一些主要常见类的源码分析,下面开始一段新的篇章,其他多线程开发设计到的一些方式,首先是ThreadLocal的源码分析。

声明:基于JDK1.8源码进行分析的。

用途上来讲,一般情况下,通过ThreadLocal.set(Object obj) 到线程中的对象obj是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。其实简单来讲,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。所以这么来看,它适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’,synchronized顺序执行是‘时间换取空间’。

类继承结构

public class ThreadLocal<T>

没有继承也没有实现。

构造方法

ThreadLocal()

    public ThreadLocal() {
    }

默认构造方法。构造方法有且只有一个。

内部类

ThreadLocalMap

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //more code ...

这是ThreadLocal的一个内部类,从源码来看,里面确实还有一个内部类Entry。 内部有一个成员属性:

 private Entry[] table;

可以想到是通过对象数组实现的。下面我们看一下他的构造方法。

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            //private Entry[] table;
            //private static final int INITIAL_CAPACITY = 16;
            //初始化数组,大小默认为16
            table = new Entry[INITIAL_CAPACITY];
            //key的hashcode & 数组长度作为下标
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //数组存入对应的对象
            table[i] = new Entry(firstKey, firstValue);
            //更新size大小
            size = 1;
            //设置阈值
            //private static final int INITIAL_CAPACITY = 16;
            setThreshold(INITIAL_CAPACITY);
        }

 内部Entry定义如下:

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            //保存对应的值
            Object value;
            //构造方法
            Entry(ThreadLocal<?> k, Object v) {
                //调用父类的构造方法
                super(k);
                value = v;
            }
        }

可以看到Entry继承了WeakReference,也就是弱引用,我们不禁联想到强引用,软引用,虚引用,和弱引用,这个已经在前面介绍过,此次不再赘述。如果entry.get()==null,意味着key不在引用,因此在table中的键值对就会被去除。 

核心方法

get()

    public T get() {
        //记录当前线程
        Thread t = Thread.currentThread();
        //调用内部getMap,后续分析
        ThreadLocalMap map = getMap(t);
        //检查是否为空,
        //如果不为空,则取出value即可,
        //如果为空,则说明此ThreadLocal随关联的键值对被垃圾回收了,
        //这里就再一次的调用了setInitialValue()方法进行初始化
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

getMap(Thread t) 

得到当前线程的ThreadLocalMap对象。

    //获取对应的map
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

setInitialValue() 

如果没有获取到map,也就是map为空的时候会调用该方法。

    private T setInitialValue() {
        T value = initialValue();
        //记录当前线程
        Thread t = Thread.currentThread();
        //获取对应的map
        ThreadLocalMap map = getMap(t);
        //如果map不为空,直接存入对应的值
        if (map != null)
            map.set(this, value);
        else
            //为空,在创建一个新的map
            createMap(t, value);
        //返回value值
        return value;
    }
    //默认返回null
    //一般情况下,这个方法至多被每个线程调用一次。
    //但是,在调用remove方法之后紧跟着调用了get方法则会又一次的调用此方法。
    protected T initialValue() {
        return null;
    }
    //重新初始化一个map
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
        

下面我们看一下map不为空的时候是怎么实现的。get()方法中源码如下:

         if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }

可以看到是调用map的getEntry方法。源码如下:

        private Entry getEntry(ThreadLocal<?> key) {
            //计算位置
            int i = key.threadLocalHashCode & (table.length - 1);
            //获取数组中对应位置元素
            Entry e = table[i];
            //如果e不为空,且key相等,返回对应的Entry对象
            if (e != null && e.get() == key)
                return e;
            else
                //e为空或者key不相等调用该方法
                return getEntryAfterMiss(key, i, e);
        }

        //参数:key, i, e
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            //记录当前数组
            Entry[] tab = table;
            //数组长度 
            int len = tab.length;
            //死循环
            //如果e为空,直接返回
            //如果不为空,则是key相等
            while (e != null) {
                //获取e对应的key
                ThreadLocal<?> k = e.get();
                //这里只是为了增加命中的几率
                if (k == key)
                    //返回e
                    return e;
                //e的key为空,情况处理
                if (k == null)
                    //调用该方法
                    expungeStaleEntry(i);
                //e存在key,但是和传入的参数key不相等
                else
                    //调用该方法,更新i
                    i = nextIndex(i, len);
                //遍历数组
                e = tab[i];
            }
            return null;
        }
        
        //根据i和len计算下一个索引
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
        
        //key为空的时候,调用该方法
        //参数为:i
        //被GC回收
        private int expungeStaleEntry(int staleSlot) {
            //记录当前数组
            Entry[] tab = table;
            //记录当前数组长度
            int len = tab.length;

            // expunge entry at staleSlot
            //tab[i].value=null 相当于将i位置的元素置空
            tab[staleSlot].value = null;
            //然后将tab[i]位置也置为空
            tab[staleSlot] = null;
            //更新维护size
            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) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        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;
        }

上面就是整个get方法的调用过程,下面我们看一下set方法的实现。

set(T value)

    public void set(T value) {
        //记录当前线程
        Thread t = Thread.currentThread();
        //获取map
        ThreadLocalMap map = getMap(t);
        //map不为空,直接调用set 
        if (map != null)
            map.set(this, value);
        else
            //初始化
            createMap(t, value);
    }

通过源码我们看到调用的方法,getMap方法就不在重复介绍了,我们看一下map的set方法的实现。

        //参数:this, value
        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.
            //记录当前数组 
            Entry[] tab = table;
            //记录当前数组长度
            int len = tab.length;
            //计算数组中的位置
            int i = key.threadLocalHashCode & (len-1);
            //对应位置的元素e
            //e不为空的时候执行循环
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                //e对应的key
                ThreadLocal<?> k = e.get();
                //如果循环的e的key和参数的key相等
                if (k == key) {
                    //更新value,直接返回
                    e.value = value;
                    return;
                }
                //key为空,直接释放
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //执行到这里说明没有找到对应的e,且找到对应的位置
            //创建一个新的Entry,并存入
            tab[i] = new Entry(key, value);
            //更新维护size
            int sz = ++size;
            //是否需要扩容处理
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

上面方法中,如果发生hash碰撞,也就是冲突了,那么采用的则是nextIndex,往后一个个寻找到第一个为空的空位,然后存放对应的元素即可。下面我们看一下remove方法的实现。

remove(ThreadLocal<?> key) 

用来移除对应的元素。

        private void remove(ThreadLocal<?> key) {
            //记录当前数组
            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)]) {//更新也是逐个往后进行查找
                if (e.get() == key) {
                    //找到该元素,直接置空,然后释放
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

下面看一个DEMO演示。

package demo;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * ThreadLocal测试例程
 * @ClassName:   ThreadLocalDemo  
 * @Description: TODO
 * @author       BurgessLee
 * @date         2019年5月6日  
 *
 */
public class ThreadLocalDemo {
	
	public static void main(String[] args) {
		AtomicInteger atomicInteger = new AtomicInteger(0);
		ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
			@Override
			protected Integer initialValue() {
				int initVal = atomicInteger.getAndIncrement();
				System.out.println("initVal= " +initVal);
				return initVal;
			}
		};
		
		for(int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() +",value="+ threadLocal.get());
				}
			},"thread"+(i+1)).start();
		}
	}
	
}

输出结果:

initVal= 1
initVal= 3
thread2,value=1
initVal= 0
thread1,value=0
thread4,value=3
initVal= 4
thread5,value=4
initVal= 2
thread3,value=2

以上就是所有源码分析和演示过程了,主要是对核心方法进行了主要的分析,如果有不对的地方还请指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值