ThreadLocal的两种典型应用场景

发布于:2023-01-13 ⋅ 阅读:(559) ⋅ 点赞:(0)

 

典型场景1:每个线程需要一个独享的对象

                    每个Thread内有自己的实例副本,不共享

                    比喻:教材只有一本书,一起做笔记有线程安全问题,复印后没问题

如下来看看SimpleDateFormat的进化之路。

模拟:

      2个线程分别用自己的SimpleDateFormat

1 . 2个线程分别用自己的SimpleDateFormat,这没问题

public class ThreadLocalNormalUsage00 {

    public static void main(String[] args) {
        new Thread (new Runnable () {
            @Override
            public void run() {
                String date = new ThreadLocalNormalUsage00 ().date (10);
                System.out.println (date);
            }
        }).start ();
        new Thread (new Runnable () {
            @Override
            public void run() {
                String date = new ThreadLocalNormalUsage00 ().date (1007);
                System.out.println (date);
            }
        }).start ();
    }
    public String date(int seconds){
        //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
        Date date  = new Date (1000*seconds);
        SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
        return dateFormat.format(date);
    }
}

打印结果如下:

2.后来延伸到30个线程

public class ThreadLocalNormalUsage001 {

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

    }
    public String date(int seconds){
        //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
        Date date  = new Date (1000*seconds);
        SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
        return dateFormat.format(date);
    }
}

 

3.但是当需求变成了1000个,那么必然要用线程池

   1000个打印日期的任务,用线程池来执行

public class ThreadLocalNormalUsage002 {
    private 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 (new Runnable () {
                 @Override
                 public void run() {
                     String date = new ThreadLocalNormalUsage002 ().date (finalI);
                     System.out.println (date);
                 }
             });

            }
             threadPool.shutdown (); //关闭线程池
        }


    public String date(int seconds){
        //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
        Date date  = new Date (1000*seconds);
        SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
        return dateFormat.format(date);
    }
    }

 

4.所有的线程都共用同一个SimpleDateFormat对象,解决了重复创建对象,浪费资源的问题

public class ThreadLocalNormalUsage003 {
    private static ExecutorService threadPool =
            Executors.newFixedThreadPool (10);
      //解决了重复创建对象,浪费资源的问题
     static  SimpleDateFormat dateFormat = new
             SimpleDateFormat ("yyyy-MM-dd hh: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 ThreadLocalNormalUsage003 ().date (finalI);
                     System.out.println (date);
                 }
             });

            }
             threadPool.shutdown (); //关闭线程池

        }


    public String date(int seconds){
        //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
        Date date  = new Date (1000*seconds);
//        SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
        return dateFormat.format(date);
    }
}

 

可以看到共用同一个SimpleDateFormat对象的时候,会造成线程不安全问题,出现了并发安全问题。

5.我们可以选择加锁synchronize避免线程不安全问题,但是效率低。

public class ThreadLocalNormalUsage004 {
    private static ExecutorService threadPool =
            Executors.newFixedThreadPool (10);
      //解决了重复创建对象,浪费资源的问题
     static  SimpleDateFormat dateFormat = new
             SimpleDateFormat ("yyyy-MM-dd hh: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 ThreadLocalNormalUsage004 ().date (finalI);
                     System.out.println (date);
                 }
             });

            }
             threadPool.shutdown (); //关闭线程池
        }


    public String date(int seconds){
        //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
        Date date  = new Date (1000*seconds);
//        SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
          String s =null;
          //加锁避免线程不安全的
        synchronized (ThreadLocalNormalUsage004.class){
             s = dateFormat.format (date);

         }
        return s;
    }
}

     

     

6.在这里更好的解决方案是使用ThreadLocal

/**
 * @author: wangxiaobo
 * @create: 2021-10-19 17:05
 * 1000个打印日期的任务,用线程池来执行
 * 利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全。
 * 高效利用内存
 **/
public class ThreadLocalNormalUsage005 {
    private static ExecutorService threadPool =
            Executors.newFixedThreadPool (10);
    //解决了重复创建对象,浪费资源的问题
//    static SimpleDateFormat dateFormat = new
//            SimpleDateFormat ("yyyy-MM-dd hh: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 ThreadLocalNormalUsage005 ().date (finalI);
                    System.out.println (date);
                }
            });

        }
        threadPool.shutdown (); //关闭线程池
    }


    public String date(int seconds) {
        //参数的单位是毫秒。从1970.1.1 00:00:00 GMT计时
        Date date = new Date (1000 * seconds);
        SimpleDateFormat simpleDateFormat =
                ThreadSafeFormatter.threadLocal.get ();

        return simpleDateFormat.format (date);
    }
}
    class ThreadSafeFormatter{
        public static ThreadLocal<SimpleDateFormat> threadLocal =new
                ThreadLocal <SimpleDateFormat>(){
                    @Override
                    protected SimpleDateFormat initialValue() {
                        return new SimpleDateFormat
                                ("yyyy-MM-dd hh:mm:ss");
                    }
                };
        /**
         * java 8之后的lambda表达式,和上面的等效
         */
        public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal
                =ThreadLocal.withInitial (()-> new SimpleDateFormat
                ("yyyy-MM-dd hh:mm:ss"));

}

总结:ThreadLocal是线程安全的

没有synchronized带来的性能问题,完全可以并行执行的,因为每个线程内都有一个独享的对象,所以不同的线程不会有相互共享的问题,就不会造成线程安全问题。

 

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到