Эта статья помогает всем понять нитолокальные, используя случаи оптимизации SimpleDateFormat, которые небезопасны в одновременных средах.
Недавно я разобрался с проектами компании и обнаружил, что написано много плохих вещей, например, следующие:
открытый класс DateUtil {Private Final Static SimpleDateFormat Sdfyhm = new SimpleDateFormat ("yyyyMmdd"); публичная синхронизированная статическая дата parseymdhms (String Source) {try {return sdfyhm.parse (source); } catch (parseexception e) {e.printstacktrace (); вернуть новую дату (); }}} Во -первых, давайте проанализируем:
Функция parseymdhms () В этой точке используется синхронизированная модификация, что означает, что операция является потоком, поэтому ее необходимо синхронизировать. Поток-UNSAFE может быть только методом Parse () SimpleDateFormat. Проверьте исходный код. В SimpleDateFormat есть глобальная переменная.
Защищенный календарный календарь; Date Parse () {Calendar.clear (); ... // выполнить некоторые операции, чтобы установить дату календаря и другого календаря.getTime (); // Получить время календаря}Операция Clear () приведет к небезопасности резьбы.
Кроме того, использование синхронизированного ключевого слова оказывает большое влияние на производительность, особенно при многопоточном режиме, каждый раз, когда называется метод 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 = "mmddhhmmssssss"; публичная окончательная статическая строка ymdhms = "yyymmddhhmmss"; публичная окончательная статическая строка ymdhms_ = "yyyy-mm-dd Hh: MM: SS"; публичная окончательная статическая строка ymd = "yyyymmdd"; публичная окончательная статическая строка ymd_ = "yyyy-mm-dd"; публичная окончательная статическая строка hms = "hhmmss"; / *** Получить экземпляр SDF соответствующего потока на основе клавиши в карте* @param pattern в карте* @return Этот экземпляр*/ private static simpledateformat getsdf (final string pattern) {threadlocal <simpledateformat> sdfthread = sdfmap.get (pattern); if (sdfthread == null) {// двойная проверка, чтобы предотвратить помещение значения в несколько раз, и причина двойного блокировки синглтона такая же, как синхронизированный (dateutil.class) {sdfthread = sdfmap.get (pattern); if (sdfthread == null) {logger.debug («Поместите новый SDF шаблона» + pattern + «на карту»); sdfthread = new Threadlocal <simpleDateFormat> () {@Override Protected SimpleDateFormat initialValue () {logger.debug ("Thread:" + thread.currentThread () + "init Pattern:" + pattern); вернуть новый SimpleDateFormat (шаблон); }}; sdfmap.put (pattern, sdfthread); }}} return sdfthread.get (); } / *** Подбор даты в соответствии с указанным шаблоном* @param Дата дата, которая будет проаналичена* @param Дата, Укажите формат* @return Date Extance* / public static date parsedate (дата строки, образец строки) {if (date == null) {thress newallargumentexception ("Дата не должна быть нулевой"); } try {return getSdf (pattern) .parse (date); } catch (parseexception e) {e.printstacktrace (); logger.error («Проанализированный формат не поддерживает:«+шаблон); } return null; } / *** Дата формата в соответствии с указанным шаблоном* @param Дата дата, которая будет отформатирована* @param Укажите формат* @return Parsed Format* / public Static String Formatdate (дата дата, строковая шаблон) {if (date == null) {throw newallalargumentExceptexpection («Дата не должна быть нулевой»); } else {return getSdf (pattern) .format (date); }}}тест
Выполните один в основном потоке и два других в детском потоке, оба используя один и тот же шаблон
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 (); }Анализ журнала
Поместите новый SDF шаблона mmddhhmmmmssssssss to mapthread: нить [Main, 5, Main] Pattern: Mmddhhmmssssthread: Thread [Thread-0,5, Main] Pattern: Mmddhhmmmssssthread: Thread [Thread-1,5, Main] init: mmddhmmmssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
анализировать
Видно, что SDFMAP введен один раз, в то время как SimpleDateFormat был новым три раза, потому что в коде было три потока. Так почему же это?
Для каждого потока потока существует глобальная ссылка переменной на Threadlocal.Threadlocalmap Threadlocals. Существует нитолокальный. Одна картина лучше тысячи слов. Структурная диаграмма выглядит следующим образом:
Таким образом, для SDFMAP структурная диаграмма будет изменена
1. Первое выполнение dateutil.formatdate (new Date (), mdhmss);
// первый раз, когда я выполнил анализ dateUtil.formatdate (new Date (), MDHMSS). Частный статический SimpleDateFormat getSdf (окончательный шаблон строки) {threadlocal <simpleDateFormat> sdfthread = sdfmap.get (pattern); // Полученная sdfthread является null, введите оператор if if (sdfthread == null) {synchronized (dateutil.class) {sdfthread = sdfmap.get (pattern); // sdfthread по -прежнему нулевой, введите оператор if if (sdfthread == null) {// print logger.debug («Поместите новый SDF шаблона» + шаблон + «карта»); // Создать Threadlocal экземпляр и переопределить метод исходной версии sdfthread = new Threadlocal <SimpleDateFormat> () {@Override Protected SimpleDateFormat initialValue () {logger.debug («Thread:« + Thread.currentThread () + »init Pattern:« + Pattern); вернуть новый SimpleDateFormat (шаблон); }}; // установить в 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 setInitialValue (); }То есть, когда значение не существует, будет вызван метод setInitialValue (), который будет вызывать метод initialValue (), который является методом, который мы переопределяем.
Соответствующая печать журнала.
Поместите новый SDF шаблона mmddhhmmmsssssss в mapthread: нить [main, 5, main] init pattern: mmddhhmmsssssssssssssssssssssssssssssssssssssss
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, полученная здесь, не является нулевым, пропустите, если Block if (sdfthread == null) {synchronized (dateutil.class) {sdfthread = sdfmap.get (pattern); if (sdfthread == null) {logger.debug («Поместите новый SDF шаблона» + pattern + «на карту»); sdfthread = new Threadlocal <simpleDateFormat> () {@Override Protected SimpleDateFormat initialValue () {logger.debug ("Thread:" + thread.currentThread () + "init Pattern:" + pattern); вернуть новый SimpleDateFormat (шаблон); }}; sdfmap.put (pattern, sdfthread); }}} // вызовать sdfthread.get () непосредственно для возврата sdfthread.get (); }Анализ sdfthread.get ()
// Выполнить `dateUtil.formatdate (new Date (), mdhmss);` public t get () {thread t = thread.currentThread (); // Получить текущий дочерний поток потока Map = getMap (t); // Карта, полученная в детском потоке, является нулевой, пропустить, если блок if (map! = Null) {threadlocalmap.entry e = map.getEntry (this); if (e! = null) {@suppresswarnings ("Unchecked") t result = (t) e.value; результат возврата; }} // Выполнить инициализацию напрямую, то есть вызов метода initialValue () Мы перезаписываем return setInitialValue (); }Соответствующий журнал:
Thread [Thread-1,5, Main] Pattern: mmddhhmmsssssssssssssssssssssssssssssssssss
Суммировать
В каком сценарии более подходит для использования Threadlocal? Кто -то дал довольно хороший ответ на Stackoverflow.
Когда и как я должен использовать трендокальную переменную?
Одним из возможных (и общего) использования является то, что у вас есть какой-то объект, который не безопасен для потока, но вы хотите избежать синхронизации доступа к этому объекту (я смотрю на вас, SimpleDateFormat). Вместо этого дайте каждому потоку собственный экземпляр объекта.
Справочный код:
https://github.com/nl101531/util-demo под Javaweb
Ссылки:
Узнайте Java Threadlocal в простом в понимании способа
Проблема и решение SimpleDateFormat's Safety и решение
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.