ThreadLocal 是不是用来解决共享资源的多线程访问的?

65 阅读2分钟

不是,ThreadLocal 并不是用来解决共享资源问题的。虽然 ThreadLocal 确实 可以用于解决多线程情况下的线程安全问题,但其资源并不是共享的,而是每个线程独享的。 ThreadLocal 解决线程安全问题的时候,相比于使用 “锁” 而言,换了一个思路,把资源变成了各线程独 享的资源,非常巧妙地避免了同步操作。具体而言,它可以在 initialValue 中 new 出自己线程独享的资 源,而多个线程之间,它们所访问的对象本身是不共享的,自然就不存在任何并发问题。这是ThreadLocal 解决并发问题的最主要思路。

如果我们把放到 ThreadLocal 中的资源用 static 修饰,让它变成一个共享资源的话,那么即便使用了 ThreadLocal,同样也会有线程安全问题。比如我们对第 44 讲中的例子进行改造,如果我们在SimpleDateFormat 之前加上一个 static 关键字来修饰,并且把这个静态对象放到 ThreadLocal 中去存 储的话,代码如下所示:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalStatic {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
    static SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalStatic().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
        return dateFormat.format(date);
    }
}

class ThreadSafeFormatter {
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return ThreadLocalStatic.dateFormat;
        }
    };
}

可以看出,00:15 被多次打印了,发生了线程安全问题。也就是说,如果我们需要放到 ThreadLocal 中的这个对象是共享的,是被 static 修饰的,那么此时其实根本就不需要用到 ThreadLocal,即使用ThreadLocal 并不能解决线程安全问题。相反,我们对于这种共享的变量,如果想要保证它的线程安全,应该用其他的方法,比如说可以使用synchronized 或者是加锁等其他的方法来解决线程安全问题,而不是使用 ThreadLocal,因为这不是ThreadLocal 应该使用的场景。