亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

java ThreadLocal使用案例詳解

2024-07-14 08:40:10
字體:
來源:轉載
供稿:網友

本文借由并發環境下使用線程不安全的SimpleDateFormat優化案例,幫助大家理解ThreadLocal.

最近整理公司項目,發現不少寫的比較糟糕的地方,比如下面這個:

public class DateUtil {  private final static SimpleDateFormat sdfyhm = new SimpleDateFormat(      "yyyyMMdd");        public synchronized static Date parseymdhms(String source) {    try {      return sdfyhm.parse(source);    } catch (ParseException e) {      e.printStackTrace();      return new Date();    }  }}

首先分析下:
該處的函數parseymdhms()使用了synchronized修飾,意味著該操作是線程不安全的,所以需要同步,線程不安全也只能是SimpleDateFormat的parse()方法,查看下源碼,在SimpleDateFormat里面有一個全局變量

protected Calendar calendar;Date parse() {  calendar.clear(); ... // 執行一些操作, 設置 calendar 的日期什么的 calendar.getTime(); // 獲取calendar的時間}

該clear()操作會造成線程不安全.

此外使用synchronized 關鍵字對性能有很大影響,尤其是多線程的時候,每一次調用parseymdhms方法都會進行同步判斷,并且同步本身開銷就很大,因此這是不合理的解決方案.

改進方法

線程不安全是源于多線程使用了共享變量造成,所以這里使用ThreadLocal<SimpleDateFormat>來給每個線程單獨創建副本變量,先給出代碼,再分析這樣的解決問題的原因.

/** * 日期工具類(使用了ThreadLocal獲取SimpleDateFormat,其他方法可以直接拷貝common-lang) * @author Niu Li * @date 2016/11/19 */public class DateUtil {  private static Map<String,ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();  private static Logger logger = LoggerFactory.getLogger(DateUtil.class);  public final static String MDHMSS = "MMddHHmmssSSS";  public final static String YMDHMS = "yyyyMMddHHmmss";  public final static String YMDHMS_ = "yyyy-MM-dd HH:mm:ss";  public final static String YMD = "yyyyMMdd";  public final static String YMD_ = "yyyy-MM-dd";  public final static String HMS = "HHmmss";  /**   * 根據map中的key得到對應線程的sdf實例   * @param pattern map中的key   * @return 該實例   */  private static SimpleDateFormat getSdf(final String pattern){    ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern);    if (sdfThread == null){      //雙重檢驗,防止sdfMap被多次put進去值,和雙重鎖單例原因是一樣的      synchronized (DateUtil.class){        sdfThread = sdfMap.get(pattern);        if (sdfThread == null){          logger.debug("put new sdf of pattern " + pattern + " to map");          sdfThread = new ThreadLocal<SimpleDateFormat>(){            @Override            protected SimpleDateFormat initialValue() {              logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern);              return new SimpleDateFormat(pattern);            }          };          sdfMap.put(pattern,sdfThread);        }      }    }    return sdfThread.get();  }  /**   * 按照指定pattern解析日期   * @param date 要解析的date   * @param pattern 指定格式   * @return 解析后date實例   */  public static Date parseDate(String date,String pattern){    if(date == null) {      throw new IllegalArgumentException("The date must not be null");    }    try {      return getSdf(pattern).parse(date);    } catch (ParseException e) {      e.printStackTrace();      logger.error("解析的格式不支持:"+pattern);    }    return null;  }  /**   * 按照指定pattern格式化日期   * @param date 要格式化的date   * @param pattern 指定格式   * @return 解析后格式   */  public static String formatDate(Date date,String pattern){    if (date == null){      throw new IllegalArgumentException("The date must not be null");    }else {      return getSdf(pattern).format(date);    }  }}

測試

在主線程中執行一個,另外兩個在子線程執行,使用的都是同一個pattern

public static void main(String[] args) {    DateUtil.formatDate(new Date(),MDHMSS);    new Thread(()->{      DateUtil.formatDate(new Date(),MDHMSS);    }).start();    new Thread(()->{      DateUtil.formatDate(new Date(),MDHMSS);    }).start();  }

日志分析

put new sdf of pattern MMddHHmmssSSS to mapthread: Thread[main,5,main] init pattern: MMddHHmmssSSSthread: Thread[Thread-0,5,main] init pattern: MMddHHmmssSSSthread: Thread[Thread-1,5,main] init pattern: MMddHHmmssSSS

分析

可以看出來sdfMap put進去了一次,而SimpleDateFormat被new了三次,因為代碼中有三個線程.那么這是為什么呢?

對于每一個線程Thread,其內部有一個ThreadLocal.ThreadLocalMap threadLocals的全局變量引用,ThreadLocal.ThreadLocalMap里面有一個保存該ThreadLocal和對應value,一圖勝千言,結構圖如下:

java,ThreadLocal

那么對于sdfMap的話,結構圖就變更了下

java,ThreadLocal

1.首先第一次執行DateUtil.formatDate(new Date(),MDHMSS);

//第一次執行DateUtil.formatDate(new Date(),MDHMSS)分析  private static SimpleDateFormat getSdf(final String pattern){    ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern);    //得到的sdfThread為null,進入if語句    if (sdfThread == null){      synchronized (DateUtil.class){        sdfThread = sdfMap.get(pattern);        //sdfThread仍然為null,進入if語句        if (sdfThread == null){          //打印日志          logger.debug("put new sdf of pattern " + pattern + " to map");          //創建ThreadLocal實例,并覆蓋initialValue方法          sdfThread = new ThreadLocal<SimpleDateFormat>(){            @Override            protected SimpleDateFormat initialValue() {              logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern);              return new SimpleDateFormat(pattern);            }          };          //設置進如sdfMap          sdfMap.put(pattern,sdfThread);        }      }    }    return sdfThread.get();  }

這個時候可能有人會問,這里并沒有調用ThreadLocal的set方法,那么值是怎么設置進入的呢?
這就需要看sdfThread.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();  }

也就是說當值不存在的時候會調用setInitialValue()方法,該方法會調用initialValue()方法,也就是我們覆蓋的方法.

對應日志打印.

put new sdf of pattern MMddHHmmssSSS to mapthread: Thread[main,5,main] init pattern: MMddHHmmssSSS

2.第二次在子線程執行DateUtil.formatDate(new Date(),MDHMSS);

//第二次在子線程執行`DateUtil.formatDate(new Date(),MDHMSS);`  private static SimpleDateFormat getSdf(final String pattern){    ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern);    //這里得到的sdfThread不為null,跳過if塊    if (sdfThread == null){      synchronized (DateUtil.class){        sdfThread = sdfMap.get(pattern);        if (sdfThread == null){          logger.debug("put new sdf of pattern " + pattern + " to map");          sdfThread = new ThreadLocal<SimpleDateFormat>(){            @Override            protected SimpleDateFormat initialValue() {              logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern);              return new SimpleDateFormat(pattern);            }          };          sdfMap.put(pattern,sdfThread);        }      }    }    //直接調用sdfThread.get()返回    return sdfThread.get();  }

分析sdfThread.get()

//第二次在子線程執行`DateUtil.formatDate(new Date(),MDHMSS);`  public T get() {    Thread t = Thread.currentThread();//得到當前子線程    ThreadLocalMap map = getMap(t);    //子線程中得到的map為null,跳過if塊    if (map != null) {      ThreadLocalMap.Entry e = map.getEntry(this);      if (e != null) {        @SuppressWarnings("unchecked")        T result = (T)e.value;        return result;      }    }    //直接執行初始化,也就是調用我們覆蓋的initialValue()方法    return setInitialValue();  }

對應日志:

Thread[Thread-1,5,main] init pattern: MMddHHmmssSSS

總結

在什么場景下比較適合使用ThreadLocal?stackoverflow上有人給出了還不錯的回答。
When and how should I use a ThreadLocal variable?
One possible (and common) use is when you have some object that is not thread-safe, but you want to avoid synchronizing access to that object (I'm looking at you, SimpleDateFormat). Instead, give each thread its own instance of the object.

參考代碼:

https://github.com/nl101531/JavaWEB 下Util-Demo

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91久久久国产精品| 国产91成人video| 国产男人精品视频| 日韩电影免费在线观看中文字幕| 国产99视频在线观看| 国内精品伊人久久| 在线观看国产精品91| 久久精品中文字幕免费mv| 国产精品www色诱视频| 成人免费视频97| 精品亚洲男同gayvideo网站| 日韩av片电影专区| 日韩视频在线免费观看| 三级精品视频久久久久| 国产丝袜高跟一区| 国产精品99免视看9| 久久精品视频在线播放| 中文字幕欧美精品日韩中文字幕| 91极品女神在线| 久久亚洲一区二区三区四区五区高| 91久久久久久久久久久久久| 国产精品美女免费| 91av视频在线| 国内精品久久久久久久久| 国产精品老牛影院在线观看| 中文字幕在线国产精品| 亚洲欧美精品伊人久久| 97视频在线观看免费高清完整版在线观看| 久久精品91久久香蕉加勒比| 国产偷国产偷亚洲清高网站| 成人午夜在线观看| 久久露脸国产精品| 日韩在线视频网站| 亚洲精品之草原avav久久| 国产精品免费视频xxxx| 成人黄在线观看| 精品久久久久久久久久国产| 亚洲国模精品一区| 九九热这里只有精品免费看| 久久手机免费视频| 国产精品久久久久久久app| 亚洲一区二区日本| 国产亚洲精品一区二区| 7m精品福利视频导航| 欧美激情videos| 欧美激情一区二区三区在线视频观看| 欧美性xxxx极品高清hd直播| 国外成人免费在线播放| 亚洲r级在线观看| 国产成人精品视频在线观看| 久久久日本电影| 久久久精品国产网站| 国产午夜一区二区| 这里只有精品视频在线| 午夜精品久久久久久久99热浪潮| 2019中文字幕在线免费观看| 欧美肥婆姓交大片| 欧美日韩中文字幕| 日韩欧美精品在线观看| 欧美一区二粉嫩精品国产一线天| 久久亚洲一区二区三区四区五区高| 欧美老肥婆性猛交视频| 欧美性猛交xxxxx水多| 国产在线播放不卡| 国产精品视频自在线| 亚洲精品天天看| 欧美日韩国产中字| 成人网在线观看| 欧美与欧洲交xxxx免费观看| 亚洲高清免费观看高清完整版| 2018国产精品视频| 欧美人在线视频| 精品久久久久久久中文字幕| 国产精品久久久久久久久久久久久久| 日韩精品中文字幕在线| 久久99久久久久久久噜噜| 亚洲最大av在线| 欧美视频中文在线看| 久久全球大尺度高清视频| 亚洲综合日韩在线| 久久国产色av| 日本成人精品在线| 日韩福利视频在线观看| 国产精品国语对白| 最近2019年手机中文字幕| 日韩欧美在线一区| 韩日欧美一区二区| 久久久这里只有精品视频| 狠狠色狠色综合曰曰| 亚洲综合第一页| 91av中文字幕| 日韩欧美成人网| 欧美性猛交xxxx免费看漫画| 亚洲精品成a人在线观看| 国产成人aa精品一区在线播放| 这里只有精品视频| 久久久久久国产精品久久| 亚洲国产精品va在线观看黑人| 美女性感视频久久久| 日韩美女写真福利在线观看| 亚洲女人天堂成人av在线| 亚洲精品www| 亚洲曰本av电影| 久久精品99久久久香蕉| 精品夜色国产国偷在线| 69久久夜色精品国产69| 中文字幕视频一区二区在线有码| 日韩av色综合| 久久成人18免费网站| 成人午夜一级二级三级| 国产精品久久久久9999| 亚洲人成电影在线播放| 成人黄色免费片| 久久久久久免费精品| 国产精品欧美日韩久久| 亚洲国产欧美一区二区丝袜黑人| 青青草国产精品一区二区| 色综合久久中文字幕综合网小说| 精品亚洲va在线va天堂资源站| 国模精品视频一区二区三区| 国产成人精品电影| 色久欧美在线视频观看| 亚洲国产欧美一区二区丝袜黑人| 欧美精品www| xxxxx91麻豆| 最近的2019中文字幕免费一页| 久久久女女女女999久久| 亚洲精品免费网站| 中文字幕亚洲一区| 日本高清久久天堂| 成人h视频在线观看播放| 亚洲男人的天堂在线| 亚洲最新中文字幕| 欧美老肥婆性猛交视频| 日本亚洲欧美成人| 另类美女黄大片| 另类天堂视频在线观看| 亚洲国产精品电影| 亚洲高清不卡av| 欧美激情奇米色| 亚洲电影在线看| 久久青草精品视频免费观看| 国产美女精品视频免费观看| 一区二区在线视频播放| 国产一区二区视频在线观看| 国外成人性视频| 欧美极品美女电影一区| 国产午夜精品美女视频明星a级| 亚洲福利在线看| 亚洲性日韩精品一区二区| 欧日韩不卡在线视频| 日韩欧美视频一区二区三区| 国产精品高清在线观看| 日韩欧美a级成人黄色| 黑人巨大精品欧美一区二区免费| 亚洲一区二区三区在线免费观看| 5566日本婷婷色中文字幕97| 日韩第一页在线| 97国产在线观看| 麻豆国产精品va在线观看不卡| 国产91色在线|| 成人激情视频在线播放| 欧美极品少妇与黑人| 国产亚洲欧美日韩一区二区|