【JUC】ThreadLocal

作用

ThreadLocal类是修饰变量的,重点是在控制变量的作用域

在多线程环境下,可以保证各个线程之间的变量互相隔离、相互独立,实现每一个线程都有自己的共享变量

结构

  • 每个Thread线程内部都有一个Map。

  • Map里面存储线程本地对象(key)和线程的变量副本(value)

  • 但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

用法

public class ErrorContext{
	private List<String> messages = new ArrayList<String>();

	private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

	private ErrorContext(){
	}

	public static ErrorContext getInstance(){
		ErrorContext context = LOCAL.get();
		if (context == null){

			context = new ErrorContext();
			LOCAL.set(context);
		}
		return context;
	}

	public ErrorContext message(String message){
		this.messages.add(message);
		return this;
	}

	public ErrorContext reset(){

		messages.clear();
		LOCAL.remove();
		return this;
	}

	@Override
	public String toString(){
		StringBuilder description = new StringBuilder();

		for (String msg : messages){
			description.append("### ");
			description.append(msg);
			description.append("\n");
		}

		return description.toString();
	}

	public static void main(String[] args){

		ErrorContext cxtMain = ErrorContext.getInstance();

		cxtMain.message("Main Thread Message");
		System.out.println(cxtMain);
		cxtMain.reset();

		Runnable task1 = () -> {

			ErrorContext cxtTask1 = ErrorContext.getInstance();
			cxtTask1.message("Task1 Thread Message");
			System.out.println(cxtTask1);
			cxtTask1.reset();
		};

		Runnable task2 = () -> {
			ErrorContext cxtTask2 = ErrorContext.getInstance();
			cxtTask2.message("Task2 Thread Message");
			System.out.println(cxtTask2);
			cxtTask2.reset();
		};

		new Thread(task1).start();

		new Thread(task2).start();

	}
}

使用场景

数据库连接管理,线程会话管理等场景,只适用于独立变量副本的情况。

内存泄露

由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

如何避免泄漏
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。

如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

父子进程

当子线程要获取父进程中的ThreadLocal值,得到的确是null,原因是子线程ThreadLocal调用get方法时的当前线程是子线程,所以取不到父线程的值。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

InheritableThreadLocal

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
   
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * 重写Threadlocal类中的getMap方法,在原Threadlocal中是返回
     *t.theadLocals,而在这么却是返回了inheritableThreadLocals,因为
     * Thread类中也有一个要保存父子传递的变量
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * 同理,在创建ThreadLocalMap的时候不是给t.threadlocal赋值
     *而是给inheritableThreadLocals变量赋值
     * 
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

如果你使用InheritableThreadLocal,那么保存的所有东西都已经不在原来的t.thradLocals里面,而是在一个新的t.inheritableThreadLocals变量中了。

父线程的所有的值都copy到子线程中,使得子线程可以获取父线程的内容。在copy过程中是浅拷贝,key和value都是原来的引用地址

过程:

  1. 在创建InheritableThreadLocal对象的时候赋值给线程的t.inheritableThreadLocals变量
  2. 在创建新线程的时候会check父线程中t.inheritableThreadLocals变量是否为null,如果不为null则copy一份ThradLocalMap到子线程的t.inheritableThreadLocals成员变量中去
  3. 因为复写了getMap(Thread)和CreateMap()方法,所以get值得时候,就可以在getMap(t)的时候就会从t.inheritableThreadLocals中拿到map对象,从而实现了可以拿到父线程ThreadLocal中的值

与sync

ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别:

synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。当然ThreadLocal并不能代替synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。

参考

https://www.jianshu.com/p/98b68c97df9b

http://www.threadlocal.cn

https://blog.csdn.net/a837199685/article/details/52712547

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值