请介绍一下ThreadLocal?

发布于 2019-12-27 10:26:22
关注者
0
被浏览
1577
1 个回答
  • 面试哥
    面试哥 2019-12-27
    为面试而生,有面试问题,就找面试哥。

    ThreadLocal简介

    通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK中提供的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

    如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get()set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

    再举个简单的例子:

    比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来这两个线程竞争的。

    ThreadLocal示例

    相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。

    import java.text.SimpleDateFormat;
    import java.util.Random;
    
    public class ThreadLocalExample implements Runnable{
    
         // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
        private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
    
        public static void main(String[] args) throws InterruptedException {
            ThreadLocalExample obj = new ThreadLocalExample();
            for(int i=0 ; i<10; i++){
                Thread t = new Thread(obj, ""+i);
                Thread.sleep(new Random().nextInt(1000));
                t.start();
            }
        }
    
        @Override
        public void run() {
            System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //formatter pattern is changed here by thread, but it won't reflect to other threads
            formatter.set(new SimpleDateFormat());
    
            System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
        }
    
    }
    
    

    Output:

    Thread Name= 0 default Formatter = yyyyMMdd HHmm
    Thread Name= 0 formatter = yy-M-d ah:mm
    Thread Name= 1 default Formatter = yyyyMMdd HHmm
    Thread Name= 2 default Formatter = yyyyMMdd HHmm
    Thread Name= 1 formatter = yy-M-d ah:mm
    Thread Name= 3 default Formatter = yyyyMMdd HHmm
    Thread Name= 2 formatter = yy-M-d ah:mm
    Thread Name= 4 default Formatter = yyyyMMdd HHmm
    Thread Name= 3 formatter = yy-M-d ah:mm
    Thread Name= 4 formatter = yy-M-d ah:mm
    Thread Name= 5 default Formatter = yyyyMMdd HHmm
    Thread Name= 5 formatter = yy-M-d ah:mm
    Thread Name= 6 default Formatter = yyyyMMdd HHmm
    Thread Name= 6 formatter = yy-M-d ah:mm
    Thread Name= 7 default Formatter = yyyyMMdd HHmm
    Thread Name= 7 formatter = yy-M-d ah:mm
    Thread Name= 8 default Formatter = yyyyMMdd HHmm
    Thread Name= 9 default Formatter = yyyyMMdd HHmm
    Thread Name= 8 formatter = yy-M-d ah:mm
    Thread Name= 9 formatter = yy-M-d ah:mm
    

    从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。

    上面有一段代码用到了创建 ThreadLocal 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法withInitial(),将Supplier功能接口作为参数。

     private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue()
            {
                return new SimpleDateFormat("yyyyMMdd HHmm");
            }
        };
    

推荐阅读
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看