ThreadLocal原理—使用

本文介绍了ThreadLocal线程局部变量的原理和使用。ThreadLocal保证了在多线程环境中每个线程都有自己的变量副本,避免了线程间的竞争。核心是ThreadLocalMap,其中Entry使用弱引用防止内存泄漏。文章详细探讨了ThreadLocal的set和get方法,以及ThreadLocalMap的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、介绍

二、原理

2.1、ThreadLocal

2.1.1、set关键方法

2.1.2、get关键方法

2.2、ThreadLocalMap

2.2.1、Entry弱引用WeakReference,防止内存泄漏

2.2.2、set关键方法

2.2.3、get关键方法

三、使用

四、总结


  • 一、介绍

ThreadLocal是指线程局部变量,多个线程并发运行的时候,使用ThreadLocal装饰的变量在每个线程里都是单独使用的,Thread线程本身就是一个类,ThreadLocal是Thread里面的一个局部变量。所以ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的和普通的类一样的。由于每个线程拥有了自己的变量,所以消除了多线程情况下的竞争关系。

  • 二、原理

ThreadLocal这个类主要做用为key泛型ThreadLocal<T>,都是作为ThreadLocalMap的key值,每个线程都要自己的一个map,map是一个数组的数据结构存储数据,每个元素是一个Entry,entry的key是threadlocal的引用,也就是当前变量的副本,value就是set的值。 

2.1、ThreadLocal

ThreadLocal这个类主要做用为key泛型ThreadLocal<T>,都是作为ThreadLocalMap的key值和hashmap实现原理一样主要一个方法threadLocalHashCode = nextHashCode(),通过hashcode计算value位置

/**
 * The next hash code to be given out. Updated atomically. Starts at
 * zero.
 */
private static AtomicInteger nextHashCode =
    new AtomicInteger();

/**
 * The difference between successively generated hash codes - turns
 * implicit sequential thread-local IDs into near-optimally spread
 * multiplicative hash values for power-of-two-sized tables.
 */
private static final int HASH_INCREMENT = 0x61c88647;

/**
 * Returns the next hash code.
 */
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

2.1.1、set关键方法

/**
* T value存入ThreadLocalMap真实值
*/
public void set(T value) {
/**
* 关键方法获取当前线程从而做到线程数据隔离
*/
    Thread t = Thread.currentThread();
/**
* 通过thread获取当前线程内对象的ThreadLocalMap,这是个数组结构
*/
    ThreadLocalMap map = getMap(t);
/**
* 如果第一次使用需要创建ThreadLocalMap
*/
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

2.1.2、get关键方法

public T get() {
/**
* 关键方法获取当前线程从而做到线程数据隔离
*/
    Thread t = Thread.currentThread();
/**
* 通过thread获取当前线程内对象的ThreadLocalMap,这是个数组结构
*/
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

2.2、ThreadLocalMap

public class Thread implements Runnable {
	.
	.
	.
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
	.
	.
	.
}

Thread线程类持有ThreadLocal.ThreadLocalMap,ThreadLocalMap类似Map实现方式里面由一个数组结构

static class ThreadLocalMap {
/**
     * 默认数组16.
     */
    private static final int INITIAL_CAPACITY = 16;

/**
     * table数组存储value
     */
    private Entry[] table;

    /**
     * table里面value size
     */
    private int size = 0;

    /**
     * 和map加载因子一样
     */
    private int threshold; // Default to 0
    /**
     * 计算扩容阈值 len 2/3 load factor.
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

2.2.1、Entry弱引用WeakReference,防止内存泄漏

tatic class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

2.2.2、set关键方法

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;
//通过位运算计算在table里面的位置和求模结果一样更加高效
    int i = key.threadLocalHashCode & (len-1);

    //如果已经存在,hash位置碰撞
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
    
//如果是同一个key直接替换返回
        if (k == key) {
            e.value = value;
            return;
        }

//重新计算位置放入素组内
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

//如果对应位置不存在直接赋值
    tab[i] = new Entry(key, value);
    int sz = ++size;

//判断是否需要扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();

2.2.3、get关键方法

private Entry getEntry(ThreadLocal<?> key) {
//获取index下标通过hashcode
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
//如果取到了值并且key相同直接返回
    if (e != null && e.get() == key)
        return e;
    else
//如果没有取到或有碰撞需要再判断
        return getEntryAfterMiss(key, i, e);
}
  • 三、使用

import java.util.Random;

public class ThreadLocalTest {
    private final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>() {
        //ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
        @Override
        protected Object initialValue() {
            System.out.println("调用get()时,当前线程共享变量没有设置,调initialValue获取默认值");
            return null;
        }
    };

    public void testThreadLocal() {
        new Thread(new MyRunnable("String测试1111",threadLocal),"thread1").start();
        new Thread(new MyRunnable(12345,threadLocal),"thread2").start();
        new Thread(new MyRunnable("String测试2222",threadLocal),"thread3").start();
        new Thread(new MyRunnable(66666,threadLocal),"thread4").start();
    }

    public static class MyRunnable<T> implements Runnable {

        private T value;
        private ThreadLocal threadLocal;
        public MyRunnable(T o,ThreadLocal threadLocal){
            value = o;
            this.threadLocal = threadLocal;
        }

        @Override
        public void run() {
            //不同线程中设置值
		threadLocal.set(value);
            try {
                Random random = new Random();
                Thread.sleep(random.nextInt(200) + 100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程" + Thread.currentThread().getName() + " threadLocal "+threadLocal.get());
        }
    }
}

 

  • 四、总结

ThreadLocal 适用于如下两种场景

  • 每个线程需要有自己单独的实例
  • 实例需要在多个方法中共享,但不希望被多线程共享

对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。

对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值