https://cloud.tencent.com/developer/article/2218317

ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。

实现每一个线程都有自己专属的本地变量副本(自己用自己的变量不麻烦别人,不和其他人共享,人人有份,人各一份),主要解决了让每个线程绑定自己的值,通过使用get()set() 方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全问题。

2.永远的helloworld

class House //资源类
{
    int saleCount = 0;

    public synchronized void saleHouse() {
        ++saleCount;
    }

    /*ThreadLocal<Integer> saleVolume = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue()
        {
            return 0;
        }
    };*/
    ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);

    public void saleVolumeByThreadLocal() {
        saleVolume.set(1 + saleVolume.get());
    }
}

/**
 * 需求1: 5个销售卖房子,集团高层只关心销售总量的准确统计数。
 * <p>
 * 需求2: 5个销售卖完随机数房子,各自独立销售额度,自己业绩按提成走,
 */
public class ThreadLocalDemo {
    public static void main(String[] args) throws InterruptedException {

        House house = new House();

        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                int size = new Random().nextInt(5) + 1;
                try {
                    for (int j = 1; j <= size; j++) {
                        house.saleHouse();
                        house.saleVolumeByThreadLocal();
                    }
                    System.out.println(Thread.currentThread().getName() + "\\\\t" + "号销售卖出:" + house.saleVolume.get());
                } finally {
                    house.saleVolume.remove();
                }
            }, String.valueOf(i)).start();
        }

        //暂停毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "\\\\t" + "共计卖出多少套: " + house.saleCount);
    }
}

小总结:

因为每个 Thread 内有自己的实例副本且该副本只由当前线程自己使用

既然其它 Thread 不可访问,那就不存在多线程间共享的问题。

统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的

  1. 加入synchronized或者Lock控制资源的访问顺序
  2. 人手一份,大家各自安好,没必要抢夺

阿里ThreadLocal规范开始

class MyData
{
    ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
    public void add()
    {
        threadLocalField.set(1 + threadLocalField.get());
    }
}

/**
.【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理
自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用
try-finally 块进行回收。
 */
public class ThreadLocalDemo2
{
    public static void main(String[] args) throws InterruptedException
    {
        MyData myData = new MyData();

        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        try
        {
            for (int i = 0; i < 10; i++) {
                threadPool.submit(() -> {
                    try {
                        Integer beforeInt = myData.threadLocalField.get();
                        myData.add();
                        Integer afterInt = myData.threadLocalField.get();
                        System.out.println(Thread.currentThread().getName()+"\\\\t"+"beforeInt:"+beforeInt+"\\\\t afterInt: "+afterInt);
                    } finally {
                        myData.threadLocalField.remove(); //不及时清理,会导致计算结果错误
                    }
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
    }
}

3.Thread,ThreadLocal,ThreadLocalMap 关系

Thread和ThreadLocal