美文网首页
ThreadLocal典型使用场景一

ThreadLocal典型使用场景一

作者: wbpailxt | 来源:发表于2020-03-11 08:25 被阅读0次
    image.png

    现在有两个任务(Task),任务的工作是根据给定的秒数格式化成“yyyy-MM-dd HH:mm:ss”。
    实现:

    /**
     * 描述:     两个线程打印日期
     */
    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(104707);
                    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);
        }
    }
    

    image.png
    /**
     * 描述:     10个线程打印日期
     */
    public class ThreadLocalNormalUsage01 {
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                int finalI = i;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        String date = new ThreadLocalNormalUsage01().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);
        }
    }
    

    image.png
    /**
     * 描述:     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(new Runnable() {
                    @Override
                    public void run() {
                        String date = new ThreadLocalNormalUsage02().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);
        }
    }
    

    我们发现1000个任务,每一个任务都new了一个SimpleDateFormat,我们完全可以只new一个实例,每次需要用的时候才去调用。

    package threadlocal;
    /**
     * 描述:     1000个打印日期的任务,用线程池来执行
     */
    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) throws InterruptedException {
            for (int i = 0; i < 1000; i++) {
                int finalI = i;
                threadPool.submit(new Runnable() {
                    @Override
                    public void run() {
                        String date = new ThreadLocalNormalUsage03().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);
            return dateFormat.format(date);
        }
    }
    

    现象:


    image.png

    这个时候问题就出现了。按道理说,每个任务的finalI都不一样,那么格式化后的时间就不可能出现相同的。可是现在偏偏出现了,为什么的?
    原因是所有线程共用一个SimpleDateFormat对象的时候,它发生了线程安全的问题。


    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) throws InterruptedException {
            for (int i = 0; i < 1000; i++) {
                int finalI = i;
                threadPool.submit(new Runnable() {
                    @Override
                    public void run() {
                        String date = new ThreadLocalNormalUsage04().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);
            String s = null;
            synchronized (ThreadLocalNormalUsage04.class) {
                s = dateFormat.format(date);
            }
            return s;
        }
    }
    

    用ThreadLocalNormalUsage04类的class对象来加锁,某一时刻只有一个线程能获得这把锁,别的线程只能等待它释放这把锁。


    使用ThreadLocal来解决


    原理是让每个线程都有自己独有的SimpleDateFormat
    /**
     * 描述:     利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存
     */
    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(new Runnable() {
                    @Override
                    public void run() {
                        String date = new ThreadLocalNormalUsage05().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");
            SimpleDateFormat dateFormat = dateFormatThreadLocal.get();
            return dateFormat.format(date);
        }
        static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
            @Override
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        };
    }
    

    ThreadLocal会在每个线程里都拥有一个SimpleDateFormat对象,也就不存在线程安全的问题,还解决了大量创建SimpleDateFormat对象的问题。
    这里每个线程的threadLocalMap的key都是这个静态ThreadLocal对象的内存地址,value是SimpleDateFormat对象,每个线程独有一个。

    相关文章

      网友评论

          本文标题:ThreadLocal典型使用场景一

          本文链接:https://www.haomeiwen.com/subject/fnewdhtx.html