ThreadLocal简介

本文通过对比线程间共享变量的使用与ThreadLocal的应用,详细解释了ThreadLocal的工作原理及其实现方式,帮助读者理解如何确保多线程环境下的线程安全性。

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

本文整理自《架构探险——从零开始写Java Web框架》一书。之前对ThreadLocal一直没怎么去了解,刚好这书里谈到了,就摘录下来,加深记忆。

什么是ThreadLocal

可以理解为一个容器,用于存放线程的局部变量,不能根据名称直译为“线程本地”或“本地线程”。把它理解成一个容器,才能比较容易理解它能够做的事情。
先看一个简单的例子:

public interface Sequence {
    int getNumber();
}

public class ClientThread extends Thread {

    private Sequence sequence;
    public ClientThread(Sequence sequence) {
        this.sequence = sequence;
    }

    @Override
    public void run() {
        for (int i=0;i<3;i++) {
            System.out.println(Thread.currentThread().getName()+" => "+
            sequence.getNumber());
        }
    }
}
public class SequenceA implements Sequence {

    private static int number = 0;

    @Override
    public int getNumber() {
        number = number+1;
        return number;
    }

    public static void main(String[] args){
        Sequence sequence = new SequenceA();

        ClientThread thread1 = new ClientThread(sequence);
        ClientThread thread2 = new ClientThread(sequence);
        ClientThread thread3 = new ClientThread(sequence);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

此时,程序的运行结果为:

Thread-0 => 1
Thread-1 => 2
Thread-0 => 3
Thread-1 => 4
Thread-0 => 5
Thread-1 => 6
Thread-2 => 7
Thread-2 => 8
Thread-2 => 9

前面由于线程启动顺序是随机的,所以并不是0、1、2的顺序。后面出现1~9,而不是1~3、1~3…重复的顺序输出的原因是,线程之间共享的static变量无法保证对于不同线程而言是安全的,也就是说,此时无法保证“线程安全”,即3个线程分别进行的getNumber不断的修改了static变量number。


这里我们再用ThreadLocal来实现:

public class SequenceB implements Sequence {

    private static ThreadLocal<Integer> numberContainer =
            new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    @Override
    public int getNumber() {
        numberContainer.set(numberContainer.get()+1);
        return numberContainer.get();
    }

    public static void main(String[] args){
        Sequence sequence = new SequenceB();

        ClientThread thread1 = new ClientThread(sequence);
        ClientThread thread2 = new ClientThread(sequence);
        ClientThread thread3 = new ClientThread(sequence);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

输出结果:

Thread-0 => 1
Thread-1 => 1
Thread-0 => 2
Thread-1 => 2
Thread-0 => 3
Thread-2 => 1
Thread-1 => 3
Thread-2 => 2
Thread-2 => 3

可以看到,当使用ThreadLocal后,对于单个线程0、1、2,都可以按1~3的顺序输出了。所以相当于这个numberContainer静态变量没有被共享,而是每个线程各一份,这样就保证了线程安全。
这里顺便说一下ThreadLocal的API:

  • public void set(T value):将值放入线程局部变量中;
  • public T get():从线程局部变量中获取值;
  • public void remove():从线程局部变量中移除值;
  • protected T initialValue():返回线程局部变量中的初始值(默认为null)

自己实现ThreadLocal

这里简单山寨一个ThreadLocal,加深对ThreadLocal的理解:

public class MyThreadLocal<T> {
    private Map<Thread,T> container = Collections.synchronizedMap(new
            HashMap<>());
    public void set(T value) {
        container.put(Thread.currentThread(),value);
    }

    public T get() {
        Thread thread = Thread.currentThread();
        T value = container.get(thread);
        if (value == null && !container.containsKey(thread)) {
            value = initialValue();
            container.put(thread,value);
        }
        return value;
    }

    public void remove() {
        container.remove(Thread.currentThread());
    }

    protected T initialValue() {
        return null;
    }
}

这个“山寨货”同样可以起到与使用ThreadLocal相同的结果。

技巧

当在一个类中使用了static成员变量的时候,一定要多问问自己,这个static成员变量需要考虑“线程安全”吗?也就是说,多个线程需要独享自己的static成员变量吗?如果需要考虑,不妨使用ThreadLocal。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值