Java多线程之ThreadLocal理解

浅谈Java多线程中对ThreadLocal的理解

大家好!今天学习了java并发编程中的ThreadLocal,特将学习心得整理起来分享给大家,如对理解有误,欢迎大家指出交流。

ThreadLocal是什么

首先先讲一下ThreadLocal是什么,当多线程在维护同一个类变量时,ThreadLocal为每一个线程创建了一个独立的该变量副本,使得每一个线程都可以独立地修改自己内部的变量副本,而不影响其他线程访问该变量。

ThreadLocal与Synchronized区别

看完ThreadLocal的概念后,很多人会想到Synchronized,那么它们两者之间究竟有啥区别呢,往下看:
ThreadLocal和Synchronized都是用于处理多线程并发访问变量的问题,不过两者处理问题的角度和方式都不同。

ThreadLocalSynchronized
思想为各个线程创建类变量的独立副本,各个线程独自处理自己的变量副本,各个线程间访问变量互不影响只提供一个变量,供各个线程排队进行访问
特点消耗部分内存,提高了并发性排队访问同一个变量,降低了程序执行效率
使用数据隔离,各线程之间访问各自变量副本互不干扰数据共享,各线程之间排队访问同一个变量

ThreadLocal的实现方式

ThreadLocal接口方法:

方法名说明
void set(Object value)设置当前线程的变量副本的值
public Object get()返回当前线程的变量副本的值
public void remove()将当前线程的变量副本删除
protected void initialValue()子类可重写该方法,为当前线程的变量副本设置初始值

下面举个例子来实现ThreadLocal:

//获取结果类
class Result implements Runnable{
    public static int num;//编号
    private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;//给编号设置初始值1
        }
    };
	
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "获取值为" + GetNum());
        }
    }

	//线程每次获取值都加1
    public int GetNum(){
        int newNum = threadLocal.get() + 1;
        threadLocal.set(newNum);
        return newNum;
    }
}

//测试类
public class ThreadLocalTest {
    public static void main(String[] args) {
        Result result = new Result();//获取数值实例
        //开启两个线程执行获取变量的值
        Thread t1 = new Thread(result);
        Thread t2 = new Thread(result);
        t1.start();
        t2.start();
    }
}

执行结果如下:
在这里插入图片描述
由此可见,线程1和线程2访问变量互不干扰,具备并发性,做到了线程隔离。

ThreadLocal原理

在jdk1.8后,ThreadLocal的实现为在Thread内部定义了一个ThreadLocalMap,是一个键-值结构,其中键为WeakReference类型的弱引用,每个ThreadLocal实例对应一个值,通过ThreadLocal接口方法来设置和获取值,结构图如下:
在这里插入图片描述

ThreadLocal内存泄漏问题

在这里插入图片描述
内存泄漏原因: 从上面的原理介绍发现ThreadLocalMap的key是弱引用类型的,也就是说使用完ThreadLocal后,ThreadLocal会被gc回收,此时Entry的key为null,但value不为null,value无法被访问,依然有CurrentThread Ref -> CurrentThread -> ThreaLocalMap -> Entry -> value在引用,Entry不会被回收,从而造成内存泄漏。
内存泄漏的两种处理方式:
(1)结束CurrentThread,因为ThreadLocalMap跟Thread的生命周期一样长,随着Thread结束,ThreadLocalMap被回收,它的Entry自然就会被回收掉,从根源上解决了问题。
(2)手动调用remove方法删除Entry,避免产生内存泄漏。
但通常在程序里会使用线程池的方式运行,线程使用完后并不会立即结束掉该线程,而是放进线程池中进行复用,那么使用第一种方式就不好处理了,因此要使用第二种方式。
ThreadLocal的解决方案: 在调用ThreadLocalMap的set/getEntry方法中,会对Entry的key进行判断,如果该Entry的key为空,则将value也置为空,这也就意味着使用完ThreadLocal,CurrentThread还在执行的前提下,就算忘记调用remove方法,弱引用的ThreadLocal也会被回收,对应的value在下一次调用set、get、remove任一方法时也会被回收,从而避免了内存泄漏。

ThreadLocal应用场景

ThreadLocal的使用场景为每个线程都要有自己的实例,常用于数据库连接、Session管理等。
例如可用于为每个线程创建数据库连接:

private static ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>() {  
    public Connection initialValue() {  
        return DriverManager.getConnection(URL);  
    }  
};  
  
public static Connection getConnection() {  
    return connectionThreadLocal.get();  
}  

好了,以上是关于对ThreadLocal的理解,可能有理解不到位的地方,欢迎大家指出交流!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值