Java并发编程:ThreadLocal

本文深入探讨了Java中的ThreadLocal,介绍了其在解决线程安全问题上的应用,对比了ThreadLocal与synchronized、ReentrantLock的区别,并剖析了ThreadLocal的内部结构和源码,强调了正确使用ThreadLocal以防止内存泄漏的重要性。

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

1. 概述

  • ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。
  • ThreadLocal的作用:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
  • 线程并发:在多线程并发的场景下。
  • 传递数据:可以通过ThreadLocal在同一线程,不同组件中传递公共变量。保存每个线程绑定的数据,在需要的地方可以直接获取,避免参数直接传递带来的代码耦合问题。
  • 线程隔离:每个线程的变量都是独立的,不会相互影响。各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失。
  • 在项目中需要进行数据传递和线程隔离的场景,可以用ThreadLocal来解决。

2. 案例分析1

package com.java.threadlocal;

/**
 * @author rrqstart
 * @Description 线程安全问题
 */
public class ThreadlLocalTest1 {
    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadlLocalTest1 t1 = new ThreadlLocalTest1();

        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    t1.setContent(Thread.currentThread().getName() + "");
                    System.out.println(Thread.currentThread().getName() + "的数据为:" + t1.getContent());
                }
            });
            thread.start();
        }
    }
}
Thread-0的数据为:Thread-1
Thread-4的数据为:Thread-4
Thread-3的数据为:Thread-3
Thread-1的数据为:Thread-1
Thread-2的数据为:Thread-2

2.1 ThreadLocal解决线程安全问题

package com.java.threadlocal;

/**
 * @author rrqstart
 * @Description 使用ThreadLocal解决线程安全问题
 */
public class ThreadLocalTest4 {
    private static ThreadLocal<String> tl = new ThreadLocal<String>();

    private String content;

    private String getContent() {
        return tl.get();
    }

    private void setContent(String content) {
        tl.set(content);
    }

    public static void main(String[] args) {
        ThreadLocalTest4 t4 = new ThreadLocalTest4();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                t4.setContent(Thread.currentThread().getName() + "");
                System.out.println(Thread.currentThread().getName() + "的数据为:" + t4.getContent());
            }).start();
        }
    }
}

2.2 synchronized解决线程安全问题

package com.java.threadlocal;

/**
 * @author rrqstart
 * @Description 加锁解决线程安全问题:synchronized
 */
public class ThreadLocalTest2 {
    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadLocalTest2 t2 = new ThreadLocalTest2();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (ThreadLocalTest2.class) {
                    t2.setContent(Thread.currentThread().getName() + "");
                    System.out.println(Thread.currentThread().getName() + "的数据为:" + t2.getContent());
                }
            }).start();
        }
    }
}

2.3 ReentrantLock解决线程安全问题

package com.java.threadlocal;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author rrqstart
 * @Description 加锁解决线程安全问题:ReentrantLock
 */
public class ThreadLocalTest3 {
    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadLocalTest3 t3 = new ThreadLocalTest3();

        ReentrantLock lock = new ReentrantLock();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    t3.setContent(Thread.currentThread().getName() + "");
                    System.out.println(Thread.currentThread().getName() + "的数据为:" + t3.getContent());
                } finally {
                    lock.unlock();
                }
            }).start();
        }
    }
}

3. ThreadLocal与synchronized的区别

synchronizedThreadLocal
原理同步机制采用以时间换空间的方式,只提供了一份变量,让不同的线程排队访问。ThreadLocal采用以空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而互不干扰。
侧重点多个线程之间访问资源的同步性多线程中让每个线程之间的数据相互隔离

4. ThreadLocal的内部结构

  • JDK早期设计:每个ThreadLocal类都创建一个Map,然后用线程的threadID作为Map的key,要存储的局部变量作为Map的value,这样就能达到各个线程的局部变量隔离的效果。

  • JDK8的设计:每个Thread维护一个ThreadLocalMap哈希表,这个哈希表的key是ThreadLocal实例本身,value才是真正要存储的值Object。
//JDK8中Thread类的部分源码
package java.lang;

public class Thread implements Runnable {
	//ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class.
	ThreadLocal.ThreadLocalMap threadLocals = null;
}
  • 每个Thread线程内部都有一个Map (ThreadLocalMap)。
  • Map里面存储ThreadLocal对象(key)和线程的变量副本(value)。
  • Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向Map获取和设置线程的变量值。
  • 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
  • JDK8中ThreadLocal设计的好处:
    • 这样设计之后每个Map存储的Entry数量就会变少,因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal的数量决定。
    • 当Thread销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用。

5. ThreadLocal的源码分析

5.1 ThreadLocal的主要方法

方法声明描述
public ThreadLocal()创建ThreadLocal对象
protected T initialValue()返回当前线程局部变量的初始值
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)创建ThreadLocal对象并设置线程局部变量的初始值
public void set(T value)设置当前线程绑定的局部变量
public T get()获取当前线程绑定的局部变量
public void remove()移除当前线程绑定的局部变量
package java.lang;

public class ThreadLocal<T> {
	public ThreadLocal() {
    }
    
    /**
    这个方法是一个延迟调用方法,从上面的代码我们得知,在set方法还未调用而先调用了get方法时才执行,并且仅执行1次。
    如果想要一个除null之外的初始值,可以重写此方法。
    (备注:该方法是一个protected的方法,显然是为了让子类覆盖而设计的)
    */
	//返回当前线程对应的ThreadLocal的初始值
	protected T initialValue() {
    	return null;
	}
	
	public T get() {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null) {
            // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 找到对应的存储实体 e 
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取存储实体 e 对应的 value值
                // 即为我们想要的当前线程对应此ThreadLocal的值
                T result = (T)e.value;
                return result;
            }
        }
        // 如果map不存在,则证明此线程没有维护的ThreadLocalMap对象
        // 调用setInitialValue进行初始化
        return setInitialValue();
    }

    private T setInitialValue() {
        // 调用initialValue获取初始化的值
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1)当前线程Thread 不存在ThreadLocalMap对象
            // 2)则调用createMap进行ThreadLocalMap对象的初始化
            // 3)并将此实体entry作为第一个值存放至ThreadLocalMap中
            createMap(t, value);
        // 返回设置的值value
        return value;
    }
    
    public void set(T value) {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value); //当前ThreadLocal的引用作为key
        else
            // 1)当前线程Thread 不存在ThreadLocalMap对象
            // 2)则调用createMap进行ThreadLocalMap对象的初始化
            // 3)并将此实体entry作为第一个值存放至ThreadLocalMap中
            createMap(t, value);
    }
    
    //删除当前线程中保存的ThreadLocal对应的实体entry
	public void remove() {
		// 获取当前线程对象中维护的ThreadLocalMap对象
		ThreadLocalMap m = getMap(Thread.currentThread());
        // 如果此map存在
		if (m != null)
			// 存在则调用map.remove
            // 以当前ThreadLocal为key删除对应的实体entry
			m.remove(this);
	}
    
    //获取当前线程Thread对应维护的ThreadLocalMap 
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; //返回当前线程的成员变量threadLocals
    }

	//创建当前线程Thread对应维护的ThreadLocalMap
	void createMap(Thread t, T firstValue) {
        //这里的this是调用此方法的threadLocal
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
}

5.2 静态内部类ThreadLocalMap

  • ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现。

package java.lang;

public class ThreadLocal<T> {
	//ThreadLocalMap使用的是延迟初始化,在第一次调用get()或者set()方法的时候才会进行初始化。
    static class ThreadLocalMap {
    	/*
    	 * 在ThreadLocalMap中,用Entry来保存K-V结构数据。
    	 * 但是Entry中key只能是ThreadLocal对象,这点被Entry的构造器限定了。
    	 * Entry继承WeakReference,使用弱引用,可以将ThreadLocal对象的生命周期和线程生命周期解绑,
    	 * 持有对ThreadLocal的弱引用,可以使得ThreadLocal在没有其他强引用的时候被回收掉,
    	 * 这样可以避免因为线程得不到销毁导致ThreadLocal对象无法被回收。
		 */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            //The value associated with this ThreadLocal.
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
            	//super(k) = super(tl) = new WeakReference(tl,queue)
            	//new出来的弱引用指向tl
                super(k); 
                value = v;
            }
        }
        
        //The initial capacity -- MUST be a power of two.
        private static final int INITIAL_CAPACITY = 16;

        //The table, resized as necessary, table.length MUST always be a power of two.
        private Entry[] table;

        //The number of entries in the table.
        private int size = 0;

        //The next size value at which to resize.
        private int threshold; // Default to 0
        
        //Set the resize threshold to maintain at worst a 2/3 load factor.
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
}

6. 案例分析2

package com.java.threadlocal;

import java.util.concurrent.TimeUnit;

public class ThreadLocalTest {
    static ThreadLocal<Person> tl = new ThreadLocal<Person>();

    static class Person {
        String name = "Curry";
    }

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(tl.get()); //null
        }).start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Person person = new Person();
            //拿到当前线程独有的map,以tl为key,以person为value,将二者装进该map中
            tl.set(person);
        }).start();
    }
}



  • 无论ThreadLocalMap中的key使用哪种类型引用都无法完全避免内存泄漏。要避免内存泄漏有两种方式:
    • 使用完ThreadLocal,调用其remove方法删除对应的Entry。
    • 使用完ThreadLocal,当前Thread也随之运行结束。
  • 相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的。
  • 也就是说,只要记得在使用完ThreadLocal之后及时的调用remove,无论key是强引用还是弱引用都不会有问题。

那么为什么key要用弱引用呢?

  • 事实上,在ThreadLocalMap中的set / getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。这就意味着使用完ThreadLocal,CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set / get / remove中的任一方法的时候会被清除,从而避免内存泄漏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值