ThreadLocal

ThreadLocal,顾名思义,这是本地线程的意思,不好意思,这次顾错了。
ThreadLocal类是用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。
按照我的理解,ThreadLocal就像是一个存储线程公共值的map,key是当前线程,value是这个公共值,各个线程之间对该公共值的使用互不影响,相互独立。

一、案例引入

举一个例子来说明一下,如每个人刚出生都是0岁,假设每个人能活100年,那么他去世的年龄就是100岁。

1、(不使用ThreadLocal情况下)

先定义一个Person类:

package com.bxw.concurrent.threadLocal;

public class Person {
    private static int age = 0;

    public void grow(){
        age++;
    }
    public int getAge(){
        return age;
    }
}

再定义一个PersonThread类:

package com.bxw.concurrent.threadLocal;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PersonThread extends Thread{
    private Person person;

    PersonThread(Person person){
        this.person = person;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"的出生年龄是" + person.getAge());
        for(int i = 0; i < 100; i++){
            person.grow();
        }
        System.out.println(Thread.currentThread().getName()+"的死亡年龄是" + person.getAge());
    }

    public static void main(String[] args) {
        Person person = new Person();
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for(int i = 0; i < 4; i++){
            executorService.submit(new PersonThread(person));
        }
        executorService.shutdown();
    }
}

运行结果:
ThreadLocal_第1张图片

可以看到四个线程的死亡年龄分别是100,200,300,400。这就说明了这四个线程共享了年龄这个静态变量,线程安全出了问题。
那么如何让每个线程都独享一个静态变量呢,看看下面对Person的改造

2、(使用ThreadLocal情况下)
package com.bxw.concurrent.threadLocal;

public class Person {
    private static int age = 0;
    
    private static MyThreadLocal ageLocal = new MyThreadLocal() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public void grow(){
        ageLocal.set(ageLocal.get() + 1);
    }
    public int getAge(){
        return ageLocal.get();
    }
}

PersonThread不用改变,再次运行,运行结果:

ThreadLocal_第2张图片

这时,每个线程的出生年龄都是0,死亡年龄都是100,说明每个线程都拥有自己的静态变量,相互独立,不相互影响。

调整下PersonThread中线程池线程个数
ExecutorService executorService = Executors.newFixedThreadPool(4);
再次运行,可以看到线程三,活了两个100年,那么线程三死亡的时候年龄是200,再次说明这个静态变量被单个线程独立使用。

ThreadLocal_第3张图片

三、ThreadLocal方法介绍

public void set(T value):将值放入线程局部变量中

public T get():从线程局部变量中获取值

public void remove():从线程局部变量中移除值

protected T initialValue():线程局部变量初始值(initialValue时protected的,就是要提醒这个方法要重写)

四、实现穷人版ThreadLocal

了解了ThreadLocal的基本原理,可以仿照ThreadLocal写个简易版的ThreadLocal

package com.bxw.concurrent.threadLocal;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class MyThreadLocal {
    private Map 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;
    }
}

将PersonThread中的ThreadLocal替换成MyThreadLocal,

private static MyThreadLocal ageLocal = new MyThreadLocal() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
};

运行结果与ThreadLocal一模一样。。。

五、JDK中使用ThreadLocal的类(比如BigDecimal)

BigDecimal中有一个静态内部类StringBuilderHelper,BigDecimal中使用到了ThreadLocal设置了初始值(一个单独的StringBuilderHelper对象),就可以实现让BigDecimal在多线程环境下,每个线程都拥有一个StringBuilderHelper对象。

ThreadLocal_第4张图片
ThreadLocal_第5张图片

六、总结

当一个类中的变量(无论是否静态)在并发条件下需要考虑线程安全,就需要使用ThreadLocal,让ThreadLocal保证每个线程都独立拥有自己的变量。

你可能感兴趣的:(ThreadLocal)