zoukankan      html  css  js  c++  java
  • ThreadLocal线程局部变量的使用

    ThreadLocal: 线程局部变量

    一)、ThreadLocal的引入

    用途:解决多线程间并发访问的方案,不是解决数据共享的方案。

    特点:每个线程提供变量的独立副本,所有的线程使用同一个ThreadLocal,

    ​ 通过ThreadLocal来创建自己的独立副本。

    好处: 解决多个线程使用同一个引用可能出现的问题。

    例如:使用SimpleDateFormat进行日期装换

    ​ 声明 public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")将日期转换对象声明为所有线程共有。

    ​ SimpleDateFormat内部维护一个Calendar对象,该对象用于存储 sdf.parse(dateStr)或sdf.format(date)的信息,通过clear()来清除设置信息,通过getTime()来获取日期信息,因为sdf为所有的线程共有,因此所有的线

    ​ 均指向同一个Calendar引用,当线程A执行完Calendar.clear(),后线程的执权到达线程B中,线程B也执行了Calendar.claer(),之后执行Calendar.getTime()得到装换的日期类,此时线程的执行权到达线程A中

    ​ 此时线程A打印的是线程B的时间,如果线程B执行完Calendar.claer()后执行权被线程A抢夺,就会出现java.lang.NumberFormatException异常。

    解决:每一个线程使用一个自己的SimpleDateFomat副本,每一个线程有自己的

    ​ Calendar

    二)、使用ThreadLocal创建独立副本的步骤

    1).创建ThreadLocal

    //T: 创建独立副本的变量类型。

    ThreadLocal<t> threadLocal = new ThreadLocal<T>();

     public ThreadLocal() {
        }
    

    2).通过set()设置当前线程的独立副本

    threadLocal.set(T t)

        public void set(T value) {
            //1.获取当前的线程对象
            Thread t = Thread.currentThread();
            //2.获取当前线程对象的map集合
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
       ThreadLocalMap getMap(Thread t) {
           // threaLocals是一个ThreadLocalMap对象。ThreadLocal.ThreadLocalMap threadLocals 
            return t.threadLocals;
        }
    
            private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    
    
    
    
    

    3.通过get()获取当前线程的独立副本

    threadLocal.get()

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    
    

    三)、使用普通方法实现多线程日期转换

    package com;
    
    /**
     * author:chenxuebing
     * Date:2019/11/1
     * Time:13:51
     */
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 不使用ThreadLocal进行时间装换时出现的问题
     *     多个线程共同使用一个sdf对象,SimpleDataFormat内部维护着一个Caledar对象
     *     Caledar对象存储着parse(dateStr)或format(date)中dateStr、date的信息
     *     Caledar执行两个操作
     *       claer(); 清楚维护的date或dateStr的信息
     *       getTime(); 获取date
     *
     *     当所有的线程共用一个SimpleDateFormat对象时,使用同一个Calendar引用
     *     此时,出现问题:
     *     线程A调用parse(), 并没有getTime(),此时轮到了线程B执行parse(),线程B执行完后也clear(),线程A, getTime()
     *     最后得到的是线程B的时间
     *
     *     ThreadLocal: 解决了static修饰的共享变量内部操作不一致的问题。
     */
    public class NormalParseDate implements Runnable{
        /**
         * 多个线程共享的变量
         */
        private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
    
        int i = 0;
    
        public NormalParseDate(int i) {
            this.i = i;
        }
    
        /**
         * 日期装换方法
         */
    
        @Override
        public void run() {
            try {
                //所有的线程使用同一个sdf
                Date date = sdf.parse("2017-08-02 11:22:"+ i % 60);
                System.out.println(i+" : "+date);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        /**
         * 创建线程
         */
        public static void main(String[] args) {
            ExecutorService executor = Executors.newFixedThreadPool(10);
            for(int i = 0; i < 100; i++){
                executor.execute(new NormalParseDate(i));
            }
        }
    }
    

    结果:

    68 : Mon Jan 02 11:22:08 CST 2017
    Exception in thread "pool-1-thread-2" java.lang.NumberFormatException: multiple points
    	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
    

    结论:因为多个线程使用同一个SimpldeDateFormat对象,指向同一个Calendar引

    ​ 用,在多线程调用时会出现数据交替问题。

    四)、使用ThreadLocal实现多线程日期转换:

    /**
     * 使用ThreadLocal线程局部变量来解决线程间共享变量内部操作不一致问题
     */
    public class ThreadLocalParseData implements Runnable{
        //线程共用一个threadLocal对象
        private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
        int i = 0;
    
        public ThreadLocalParseData(int i) {
            this.i = i;
        }
    
        @Override
        public void run() {
            //判断当前线程是否有SimpleDateFormat对象,有则使用,没有则创建
            if(threadLocal.get() == null){
                //如果没有就给当前线程创建一个, 每一个线程都有一个自己的SimpleDateFormat对象
                threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
            }
            //有,则直接使用
            try {
                Date date = threadLocal.get().parse("2017-2-21 14:29:" + i % 60);
                System.out.println(i+":"+date);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            //创建线程对象,每个线程都有一个SimpleDateFormat对象
            ExecutorService executor = Executors.newFixedThreadPool(10);
            for(int i = 0; i < 100; i++){
                executor.execute(new ThreadLocalParseData(i));
            }
        }
    }
    
    
    金麟岂能忍一世平凡 飞上了青天 天下还依然
  • 相关阅读:
    Minimum Inversion Number
    作业四
    牛客小白月赛18 G Forsaken的三维数点
    The Accomodation of Students HDU
    03-Bootstrap学习
    jquery 单击和双击事件冲突解决方案
    13-JS中的面向对象
    12-关于DOM操作的相关案例
    IO多路复用
    python读取excel文件
  • 原文地址:https://www.cnblogs.com/Auge/p/11772569.html
Copyright © 2011-2022 走看看