线程的局部变量——ThreadLocal

ThreadLocal提供了线程局部变量,确保每个线程都有自己独立的副本,避免线程间的数据干扰。本文介绍了ThreadLocal的工作原理、常见操作如get()、set()、remove(),并给出了使用场景,例如参数传递优化和管理数据库连接池。

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

ThreadLocal是什么

对这个词语分解,将其分为Thread和Local,顾名思义便是本线程的变量,既然是当前线程的变量,那么就意味着这个变量对于其他线程来说就是隔离的,也就是不可见的,ThreadLocal对每一个线程都有一个副本,以确保每一个线程都能且只能访问自己线程内部的副本变量

ThreadLocal如何实现

听起来这个ThreadLocal是不是也是比较容易懂的,那么吗,ThreadLocal又是怎么实现的呢?
在最早期,ThreadLocal的实现方式就是利用类内部的一个线程安全的Map,这个Map的键是线程的ID,值是实例的对象。这样就使得线程之间互不干扰,能起到隔离的效果,但这样做会有一个缺点:
多线程的情况之下,多个线程会竞争同一个map的键,竞争产生了,那么就得进行加锁处理,这样会耗费比较多的资源,除此之外,这种方式也可能造成内存的泄露问题。
而为了解决这些缺点,我们可以有另一种方式实现ThreadLocal,可以简单的理解为上述方式“反过来”,线程对象内部存放一个Map,而以ThreadLocal作为Map的键。这样的话就能避免竞争问题,而这样,每个线程访问的都是自己内部的map,这也是现在的ThreadLocal使用的实现方式。

ThreadLocal举例

先举一个简单的例子吧,这样能使后面的源码分析起来更加轻松一点
ThreadLocal既然是线程变量,那自然,在单线程的程序中是没有什么用处的,举一个多线程的例子:

package com.hhw;

public class ThreadLocalDemo {
    public static void main(String[] args) {
        final ThreadLocalT th = new ThreadLocalT();
        Thread t1 = new Thread("t1"){
            @Override
            public void run(){
                th.set();
                System.out.println(th.get());
            }
        };
        Thread t2 = new Thread("t2"){
            @Override
            public void run() {
                System.out.println(th.get());
            }
        };
        t1.start();
        t2.start();
    }
}

class ThreadLocalT{
    ThreadLocal<Long> threadId = new ThreadLocal<Long>();
    public void set(){
        threadId.set(Thread.currentThread().getId());
    }
    public Long get(){
        return threadId.get();
    }
}

结果如下:

11
null

Process finished with exit code 0

第一个线程,set了其id,为线程id,第二个线程为set其id。
当用get分别取出两个线程的id时,第一个线程是有值的,而第二个线程是null
这便足以阐述ThreadLocal的线程隔离,即对不同的线程有不同的副本储存
下面对照例子来分析ThreadLocal的源码

ThreadLocal简单源码分析

1、构造方法

public ThreadLocal() {
    }

首先便是构造方法,ThreadLocal的构造方法只有一个无参方法,且是个空函数
表示仅仅创建一个本地变量
也即上述例子之中的

ThreadLocal<Long> threadId = new ThreadLocal<Long>();

2、get()/set()方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
public T get() {
        Thread t = Thread.currentThread();
        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();
    }

先看set()方法,三步走:
1、首先获得当前的线程
2、再将当前线程调用getMap()方法返回一个map,类型为ThreadLocalMap
3、最后判断map是否为空,如果不是就把map的值设置为参数,否则调用createMap
然后看一下get()方法,一样是三步:
1,2步和set()一样,都是先获取线程,然后调用getMap()方法,返回一个ThreadLocalMap类型的map对象
3、最后判断一下map是否为空,如果不为空就获取当前的ThreadLocal键值对应的entry,并返回value
如果为空就直接调用setIntialValue()并返回
3、ThreadLocalMap
发现,set()/get()方法中均出现有ThreadLocalMap这一个类,这是ThreadLocal的一个内部类,查看一下该类的源码可以发现:这个类严格意义上并不能算是Map,它并没有实现Map接口
它的底层是一个Entry类型的数组,
Entry源码:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

可见,它是一个继承了一个弱引用(方便GC)的结构,其中将ThreadLocal作为键,一个Object类型的v作为值
4、remove()
除了get()和set()方法外,ThreadLocal还有一个比较重要的方法就是remove()了,这个方法能帮助我们对内存进行一定的管理,从而防止恐怖的内存泄露的发生

private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

简单来说就是删除ThreadLocal中的Entry对象。

ThreadLocal使用场景

说了这么多,实际应用当中的ThreadLocal能给我们帮什么忙呢
1、参数的传递避免
想象一个场景,不同的学生之间,会有不同的成绩,当多级(老师,教务等)之间都要查询学生成绩时,每次都要查询一个学生的各科成绩,会比较麻烦,但ThreadLocal就可以将学生的所有成绩储存在其中,并且不会与其他学生混淆,这样就更加方便了。
2、管理数据库连接池
我们可以将数据库连接池交给ThreadLocal进行管理,使得统一线程在任意时候进行的操作都是使用一个连接,保证了事务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值