TheadLocal可以为各个线程提供各自的实例。例如SimpleDateFormat是非线程安全的
假设有一个静态变量
public static final SimpleDateFormat dateFormat = new SimpleDateFormat(‘yyyy-MM-dd);
如果两个线程都执行下列操作:
String dateStamp = dateFormat.format(new Date());
结果可能混乱,因为dateFormat使用的内部数据结构可能会被并发的访问所破坏。当然也可以使用同步,不过也可以使用同步,但是开销很大;或者也可以在需要时构造一个局部SimpleDateFormat对象,不过这也太浪费了
要为每个线程构造一个实例,可以使用以下代码
public static final ThreadLocal dateFormat = ThreadLocal.withInitial()->new SimpleDateFormat(“yyyy-MM-dd”);
要访问具体的格式化方法,使用
String dateStamp = dateFormat.get().format(new Date));
在一个线程中首次调用get时,会调用initalValue方法,在此之后,get方法会返回属于当前线程的那个实例
在多线程生成随机数也存在类似的问题。Java.util.Random类是线程安全的。但是如果多个线程需要等待一个共享的随机数生成器,这会很低效
可以使用ThreadLocal类为各个线程提供一个单独的生成器,不过JDK7提供了另外一个便利类,只需要做以下调用
int random = ThreadLocalRandom.current().nextInt(upperBound);
ThreadLocalRandom.current()会返回特定于当前线程的Random实例

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本

java多线程情况下全局变量值偶尔会报空指针_java


概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,
API说明
initialValue

protected T initialValue()
返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get() 方法访问变量时将调用此方法,但如果线程之前调用了 set(T) 方法,则不会对该线程再调用 initialValue 方法。通常,此方法对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。
该实现返回 null;如果程序员希望线程局部变量具有 null 以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。

返回:
返回此线程局部变量的初始值

get

public T get()
返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。
返回:
此线程局部变量的当前线程的值

set

public void set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,它们只依靠 initialValue() 方法来设置线程局部变量的值。
参数:
value - 存储在此线程局部变量的当前线程副本中的值。

remove

public void remove()
移除此线程局部变量当前线程的值。如果此线程局部变量随后被当前线程 读取,且这期间当前线程没有 设置其值,则将调用其 initialValue() 方法重新初始化其值。这将导致在当前线程多次调用 initialValue 方法
下面是使用的例子

class MyRunnable implements Runnable {

    private int ticket = 600;
    private Object obj = new Object();
    private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {

            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    public void run() {
        while (true) {
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName() + "sale  " + ticket--);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } else {
                    break;
                }
            }

        }
    }

}