源码浅析ThreadLocal类

本文详细介绍了ThreadLocal类在并发编程中的应用,包括其如何为每个线程提供独立的变量副本,以及其内部实现机制。通过实例代码和源码分析,解释了ThreadLocal如何在不同线程间存储并获取数据,以及在未设置值时的行为。

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

ThreadLocal类的简单介绍

在并发编程中我们经常有共享资源的需求,而通过用锁的形式来保证资源的安全在多个方法共同读写同一资源时很难得到保证。再者,若全局共享一份资源,根据访问者的不同来处理不同的逻辑也将变的很困难。这时我们的ThreadLocal类就可以大显身手了,它可以为使用相同变量的每个线程都创建不同的副本。说起来总是很抽象,我们先来看一段实例代码:

public class ThreadLocalTest {
	private static ThreadLocal<String> myData = new ThreadLocal<String>();

	public static void main(String[] args) {
		myData.set("主线程存储内容");
		
		
		new Thread(new Runnable() {
			public void run() {
				myData.set("子线程1存储内容");
				System.out.println(myData.get());
			}
		},"thread_1").start();
		
		
		new Thread(new Runnable() {
			public void run() {
				myData.set("子线程2存储内容");
				System.out.println(myData.get());
			}
		},"thread_2").start();
		
		
		
		new Thread(new Runnable() {
			public void run() {
				System.out.println(myData.get());
			}
		},"thread_3").start();
		
		System.out.println(myData.get());
		
	}
}

输出:

子线程1存储内容
主线程存储内容
null
子线程2存储内容
代码很好理解,但是有没有感觉一点困惑?对,在不同的线程访问同一对象会返回不同的结果,并且如果在当前线程没有为这个对象来设置值的话,那么将默认返回null。显然ThreadLocal类为每一个线程单独储存了一份数据。那么其内部又是如何实现的?请看源码分析:


ThreadLocal源代码分析

为了更好的理解代码的意图也避免看一些细枝末节的源代码看的心烦,我们不去深究代码的一些细节,并且从我们第一次ThreadLocal的地方来入手。由上面的例子可知,我们第一次调用的ThreadLocal的方法是set,我们先来看一下它的代码:

 public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
一步一步的来分析这段代码:

首先,获得了当前调用此方法的线程对象,然后调用了values方法,并将其传入,来看一下values方法的代码:

Values values(Thread current) {
        return current.localValues;
    }
返回了线程类的localValues对象,我们接着打开线程类来找找这个对象:

ThreadLocal.Values localValues;
线程类中只声明了这个对象的引用,并且在之后的任何地方都没有用到它,那我们得到的这个values对象也就是null了。继续看,很顺利的进入到了if代码块中,我们来看一下initializeValues方法:

 Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }
为当前线程中的localValues引用new了对象。而且这个对象的类型是Values竟是ThreadLocal中的类:

/**
     * Per-thread map of ThreadLocal instances to values.
     */
    static class Values {
    }
感觉非常的奇怪,为什么绕了一个这么大的圈子然后最后new出了一个静态的内部类?仔细想一下便不难理解,因为ThreadLocal要为每个线程单独的保存数据,那么这个保存数据的对象就不能作为它的对象存在,不然也就没啥意义了,那么最好的方法也就是作为线程类的对象存在。现在values也有了,然后调用了put方法,我们看一下源码:

        void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

我们挑重点的看一下,首先通过key和mask找到了一个位置(index),而后从table数组中找到这个位置中存放的数据。下面在if语句中判断这个数据是不是等于key.reference,那么这个key是什么,reference有事什么?可以看到,key是set方法传入的参数,而调用set方法是传入的参数是this,也就是当前对象本身。而reference参数可以看一下定义的源码:
 private final Reference<ThreadLocal<T>> reference
            = new WeakReference<ThreadLocal<T>>(this);

很显然我们之前没有设置过,现在的k并没有等于当前的reference,继续向下来看,当k为空时,给k设置了值,并且将我们想要放到ThreadLocal中的对象放到了table数组中index+1的位置。set方法现在应该是很清晰了,我们来看一下get:

public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

有了刚才的分析,我们不难看懂上面的代码,它将我们刚才放进去的值取出来了。但是如果values为空的时候返回的是什么?我们来看一下getAfterMiss方法:

 Object getAfterMiss(ThreadLocal<?> key) {
            ...
            if (table[index] == null) {
                Object value = key.initialValue();

                // If the table is still the same and the slot is still empty...
                if (this.table == table && table[index] == null) {
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;

                    cleanUp();
                    return value;
                }

                // The table changed during initialValue().
                put(key, value);
                return value;
            }
            ...
        }

方法比较长,我截取出了其中我们想要的一段,如果有兴趣读者可以自己去细致分析。在代码段里显示返回了key.initialValue()方法的返回值,我们来看一下:

protected T initialValue() {
        return null;
    }

这个返回了个null,不知道大家还记不记得最开始贴出的示例代码的运行结果,在没有set值时,get方法返回的是null,这也印证了我们的结论。



此压缩包包含了本毕业设计项目的完整内容,具体包括源代码、毕业论文以及演示PPT模板。 开发语言:Java 框架:SSM(Spring、Spring MVC、MyBatis) JDK版本:JDK 1.8 或以上 开发工具:Eclipse 或 IntelliJ IDEA Maven版本:Maven 3.3 或以上 数据库:MySQL 5.7 或以上 项目配置完成后即可运行,若需添加额外功能,可根据需求自行扩展。 运行条件 确保已安装 JDK 1.8 或更高版本,并正确配置 Java 环境变量。 使用 Eclipse 或 IntelliJ IDEA 打开项目,导入 Maven 依赖,确保依赖包下载完成。 配置数据库环境,确保 MySQL 服务正常运行,并导入项目中提供的数据库脚本。 在 IDE 中启动项目,确认所有服务正常运行。 主要功能简述: 请假审批流程:系统支持请假申请的逐级审批,包括班主任审批和院系领导审批(针对超过三天的请假)。学生可以随时查看请假申请的审批进展情况。 请假记录管理:系统记录学生的所有请假记录,包括请假时间、原因、审批状态及审批意见等,供学生和审批人员查询。 学生在线请假:学生可以通过系统在线填写请假申请,包括请假的起止日期和请假原因,并提交给班主任审批。超过三天的请假需经班主任审批后,再由院系领导审批。 出勤信息记录:任课老师可以在线记录学生的上课出勤情况,包括迟到、早退、旷课和请假等状态。 出勤信息查询:学生、任课老师、班主任、院系领导和学校领导均可根据权限查看不同范围的学生上课出勤信息。学生可以查看自己所有学年的出勤信息,任课老师可以查看所教班级的出勤信息,班主任和院系领导可以查看本班或本院系的出勤信息,学校领导可以查看全校的出勤信息。 出勤统计与分析:系统提供出勤统计功能,可以按班级、学期等条件统计学生的出勤情况,帮助管理人员了解学生的出勤状况。 用户管理:系统管理员负责管理所有用户信息,包括学生、任课老师、班主任、院系领导和学校领导的账号创建、权限分配等。 数据维护:管理员可以动态更新和维护系统所需的数据,如学生信息、课程安排、学年安排等,确保系统的正常运行。 系统配置:管理员可以对系统进行配置,如设置数据库连接参数、调整系统参数等,以满足不同的使用需求。 身份验证:系统采用用户名和密码进行身份验证,确保只有授权用户才能访问系统。不同用户型(学生、任课老师、班主任、院系领导、学校领导、系统管理员)具有不同的操作权限。 权限控制:系统根据用户型分配不同的操作权限,确保用户只能访问和操作其权限范围内的功能和数据。 数据安全:系统采取多种措施保障数据安全,如数据库加密、访问控制等,防止数据泄露和非法访问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值