深入理解Java ThreadLocal:原理、示例与应用场景

Java的ThreadLocal用于创建线程本地变量。在Java中,一个对象的所有线程共享其变量,因此这些变量不是线程安全的。虽然我们可以使用同步机制来实现线程安全,但如果希望避免同步带来的性能开销,可以使用ThreadLocal变量。

Java 的 ThreadLocal

每个线程都拥有自己独立的ThreadLocal变量,可以通过get()和set()方法获取默认值或更改其值,这些值仅与当前线程关联。ThreadLocal实例通常作为希望将状态与线程相关联的类中的私有静态字段使用。

Java中的ThreadLocal示例

下面是一个简单示例,展示了如何在Java程序中使用ThreadLocal,并证明每个线程都拥有自己独立的ThreadLocal变量副本。ThreadLocalExample.java

package com.Olivia.threads;

import java.text.SimpleDateFormat;
import java.util.Random;

public class ThreadLocalExample implements Runnable{

    // SimpleDateFormat不是线程安全的,所以为每个线程提供一个实例
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };
    
    public static void main(String[] args) throws InterruptedException {
        ThreadLocalExample obj = new ThreadLocalExample();
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("线程名称= "+Thread.currentThread().getName()+" 默认格式化器 = "+formatter.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //此处线程更改了格式化器模式,但不会影响其他线程
        formatter.set(new SimpleDateFormat());
        
        System.out.println("线程名称= "+Thread.currentThread().getName()+" 格式化器 = "+formatter.get().toPattern());
    }

}

上述Java ThreadLocal示例程序的输出结果如下:

线程名称= 0 默认格式化器 = yyyyMMdd HHmm
线程名称= 1 默认格式化器 = yyyyMMdd HHmm
线程名称= 0 格式化器 = M/d/yy h:mm a
线程名称= 2 默认格式化器 = yyyyMMdd HHmm
线程名称= 1 格式化器 = M/d/yy h:mm a
线程名称= 3 默认格式化器 = yyyyMMdd HHmm
线程名称= 4 默认格式化器 = yyyyMMdd HHmm
线程名称= 4 格式化器 = M/d/yy h:mm a
线程名称= 5 默认格式化器 = yyyyMMdd HHmm
线程名称= 2 格式化器 = M/d/yy h:mm a
线程名称= 3 格式化器 = M/d/yy h:mm a
线程名称= 6 默认格式化器 = yyyyMMdd HHmm
线程名称= 5 格式化器 = M/d/yy h:mm a
线程名称= 6 格式化器 = M/d/yy h:mm a
线程名称= 7 默认格式化器 = yyyyMMdd HHmm
线程名称= 8 默认格式化器 = yyyyMMdd HHmm
线程名称= 8 格式化器 = M/d/yy h:mm a
线程名称= 7 格式化器 = M/d/yy h:mm a
线程名称= 9 默认格式化器 = yyyyMMdd HHmm
线程名称= 9 格式化器 = M/d/yy h:mm a

从输出结果可以看出,线程0已经更改了格式化程序的值,但线程2的默认格式化程序仍然保持初始值。其他线程也表现出相同的模式。更新说明:ThreadLocal类在Java 8中引入了新的withInitial()方法,该方法接受Supplier函数接口作为参数。因此,我们可以使用lambda表达式更简洁地创建ThreadLocal实例。例如,上述formatter ThreadLocal变量可以简化为一行定义:

private static final ThreadLocal<SimpleDateFormat> formatter = 
	ThreadLocal.<SimpleDateFormat>withInitial
	(() -> {return new SimpleDateFormat("yyyyMMdd HHmm");});

如果您对Java 8的特性还不熟悉,建议查阅Java 8特性相关文档以及Java 8函数接口的详细说明。关于Java编程中的ThreadLocal就介绍到这里。参考资料:Java API文档。

bannerAds