ThreadLocal源码分析

ThreadLocal源码分析

ThreadLocal是解决线程安全问题的一种方法,它通过为每个线程提供一个独立的变量副本避免了变量并发访问的冲突问题。一个ThreadLocal变量只与当前自身线程相关,对其他线程是隔离的。下面这段代码展示了ThreadLocal的使用。

public class test {
    private static final ThreadLocal<Object> tl = new ThreadLocal<>();

    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,10,1L, TimeUnit.MINUTES,new ArrayBlockingQueue<>(100));
        for (int i = 0; i < 5; i++) {
            MyRunnable runnable = new MyRunnable(i);
            poolExecutor.execute(runnable);
        }
        poolExecutor.shutdown();
        while (!poolExecutor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }

    public static class MyRunnable implements Runnable{
        private int id;
        public MyRunnable(int id){
            this.id=id;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"set()前:"+tl.get());
            tl.set(id);
            System.out.println(Thread.currentThread().getName()+"set()后:"+tl.get());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
执行结果:
pool-1-thread-2set()前:null
pool-1-thread-5set()前:null
pool-1-thread-5set()后:4
pool-1-thread-1set()前:null
pool-1-thread-4set()前:null
pool-1-thread-3set()前:null
pool-1-thread-4set()后:3
pool-1-thread-1set()后:0
pool-1-thread-2set()后:1
pool-1-thread-3set()后:2
Finished all threads

这段代码中定义了5个线程对同一个ThreadLocal进行get和set操作,但是每个线程get到的值都是线程set后的值。可见,ThreadLocal做到了多线程的数据隔离。下面看看ThreadLocal的源码。先看ThreadLocal的set()方法。

public class ThreadLocal<T> {
    
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //将value添加到map,key是当前ThreadLocal的引用
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
    
    ThreadLocalMap getMap(Thread t) {
        //每个线程都有一个名为threadLocals的ThreadLocalMap对象
        return t.threadLocals;
    }
    
    static class ThreadLocalMap {
        
        private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //计算当前ThreadLocal在数组中的索引
            int i = key.threadLocalHashCode & (len-1);
			
            //找到一组具有相同hash的key
            //从这也能看出,ThreadLocalMap使用线性探测法解决哈希冲突
            for (Entry e = tab[i];
                e != null;
                e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
				
                //如果是同一个ThreadLocal,就直接覆盖原来的值
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果key是null,说明key被回收了,替换掉它
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//如果key的hash地址是空的,那就直接插入
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                //扩容
                rehash();
        }
	}  
}

根据ThreadLocal的源码,也能推断出ThreadLocal的数据结构了。

image-20240903162015072

接下来在看一下get()方法:

public class ThreadLocal<T> {
    public T get() {
    	//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//取出当前ThreadLocal的键值对元素
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //返回当前ThreadLocal变量值
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    private Entry getEntry(ThreadLocal<?> key) {
        //计算hash地址
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        //没有哈希冲突直接返回
        if (e != null && e.get() == key)
            return e;
        //发生了哈希冲突
        else
            return getEntryAfterMiss(key, i, e);
    }
    
    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)
                expungeStaleEntry(i);
            else
                //线性探测下一个索引地址
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
}

ThreadLocal的内存泄露问题:

ThreadLocalMap中的key是ThreadLocal的弱引用,弱引用不会阻止垃圾回收器回收ThreadLocal实例,当ThreadLocal被回收后,value仍然存在,他们会占用内存,但是却无法通过ThreadLocal来访问,这就造成了内存泄露。因此,在使用完ThreadLocal变量后,可以及时remove掉这个ThreadLocal关联的键值对。

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}
posted @ 2024-09-12 13:25  宁夏路东  阅读(18)  评论(0)    收藏  举报