Java

为什么要使用 ThreadLocal ?

勤劳的小蜜蜂 · 12月26日 · 2019年

ThreadLocal 有两大典型的使用场景

  • 典型场景 1:每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有 SimpleDateFormate 和 Random),避免线程不安全
  • 典型场景 2:每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦

初始业务

我们先来看一个小业务,没啥实际的意义只是为了说明问题

有这样一个函数:输入秒数,就会通过 SimpleDateFormat 格式为 yyyy-MM-dd hh:mm:ss

业务初步升级

2 个线程分别用自己的 SimpleDateFormat,这很简单,只需要写两个 new Thread 解决

public class ThreadLocalNormalUsage00 {
    public static void main(String[] args) {
        new Thread(()-> {
            String date = new ThreadLocalNormalUsage00().date(10);
            System.out.println(date);
        }).start();

        new Thread(()-> {
            String date = new ThreadLocalNormalUsage00().date(1007);
            System.out.println(date);
        }).start();

    }

    public String date(int seconds) {
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return dateFormat.format(date);
    }
}
}

业务又升级

2 个线程延伸到 10 个线程,没问题,加个 for 循环就搞定

public class ThreadLocalNormalUsage01 {

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 30; i++) {
            int finalI = i;
            new Thread(() -> {
                String date = new ThreadLocalNormalUsage01().date(finalI);
                System.out.println(date);
            }).start();
            Thread.sleep(100);
        }

    }

    public String date(int seconds) {
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return dateFormat.format(date);
    }
}

业务又双叒叕升级

10 个线程变成 1000 个线程,问题不大,使用线程池

public class ThreadLocalNormalUsage02 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);

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

    }

    public String date(int seconds) {
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return dateFormat.format(date);
    }
}

还可以继续优化

调用 1000 次就要 new 1000 个 SimpleDateFormat,将 SimpleDateFormat 设置为静态的变量

public class ThreadLocalNormalUsage03 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    public static void main(String[] args){
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(() -> {
                String date = new ThreadLocalNormalUsage03().date(finalI);
                System.out.println(date);
            });
        }
        threadPool.shutdown();

    }

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

问题出现

多个线程调用同一个 SimpleDateFormat 对象出现了线程不安全问题

没事小问题,既然线程不安全,那么就给你加锁。

public class ThreadLocalNormalUsage04 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    public static void main(String[] args){
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(() -> {
                String date = new ThreadLocalNormalUsage04().date(finalI);
                System.out.println(date);
            });
        }
        threadPool.shutdown();

    }

    public String date(int seconds) {
        Date date = new Date(1000 * seconds);
        String s = null;
        synchronized (ThreadLocalNormalUsage04.class) {
            s = dateFormat.format(date);
        }
        return s;
    }
}

线程是安全了,但是性能却大大的下降了,后续线程都要排队等待锁的释放。

那有没有既是线程安全的,有没有性能问题的方法呢?

有,关键词 ThreadLocal

ThreadLocal 解决问题

public class ThreadLocalNormalUsage05 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);

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

    }

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

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };
}
0 条回应