Este artigo ajuda todos a entender o Threadlocal usando casos de otimização do SimpleDateFormat que não são seguros em ambientes simultâneos.
Recentemente, resolvi os projetos da empresa e descobri que há muitas coisas ruins escritas, como as seguintes:
classe pública dateUtil {private final estático SimpledateFormat sdfyhm = new SimpleDateFormat ("yyyymmdd"); Public Sincronized Data estática parseymdhms (String fonte) {try {return sdfyhm.parse (fonte); } catch (parseException e) {e.printStackTrace (); retornar nova data (); }}} Primeiro, vamos analisar:
A função parseymdhms () nesse ponto usa modificação sincronizada, o que significa que a operação é insegura de rosca, por isso precisa ser sincronizada. O Thread-insafe pode ser apenas o método parse () do SimpleDateFormat. Verifique o código -fonte. Existe uma variável global no SimpleDateFormat.
calendário protegido calendário; data parse () {calendar.clear (); ... // Execute algumas operações para definir a data do calendário e outros calendários.gettime (); // Obtenha o tempo do calendário}A operação clear () causará thread insegura.
Além disso, o uso da palavra-chave sincronizada tem um grande impacto no desempenho, especialmente quando é chamado toda vez que o método parseymdhms é chamado, o julgamento da sincronização será feito e a sincronização em si é muito cara, portanto, esta é uma solução irracional.
Método de melhoria
O encadeamento inseguro é causado pelo uso de variáveis compartilhadas por vários threads, então aqui usamos ThreadLocal <SimpleDateFormat> para criar uma variável de cópia para cada thread separadamente. Primeiro, forneça o código e depois analise a causa desse problema para resolver o problema.
/** * A classe de ferramenta de data (Threadlocal é usada para obter o SimpleDateFormat, e outros métodos podem ser copiados diretamente por Lang Common) * @Author niu li * @date 2016/11/19 */public Logger estático privado = LoggerFactory.getLogger (dateUtil.class); Public final Static String mdhms = "mmddhhmmsssss"; public final estático string ymdhms = "yyymmddhhmmss"; Public final Static String ymdhms_ = "yyyy-mm-dd hh: mm: ss"; public final estático string ymd = "yyyymmdd"; public final estático string ymd_ = "yyyy-mm-dd"; Public final Static String HMS = "HHMMSS"; / *** Obtenha a instância SDF do encadeamento correspondente com base na chave no mapa* @param Pattern Key no mapa* @return esta instância*/ private static simpledateFormat getSdf (Final String Pattern) {threadlocal <SimpledAtateFormat> sdfThread = sdfmap.get (padrão); if (sdfthread == null) {// Verifique duas vezes para impedir que o sdfmap coloque o valor em várias vezes, e o motivo do singleton de bloqueio duplo é o mesmo que sincronizado (dateUtil.class) {sdfthread = sdfmap.get (padrão); if (sdfthread == null) {logger.debug ("Coloque um novo sdf de padrão" + padrão + "para mapear"); sdfthread = new Threadlocal <SimpledateFormat> () {@Override Protected SimpleDateFormat InitialValue () {Logger.debug ("Thread:" + Thread.currentThread () + "init padrão:" + padrão); retornar novo SimpleDateFormat (padrão); }}; sdfmap.put (padrão, sdfthread); }}} return sdfthread.get (); } / *** Analisando a data de acordo com o padrão especificado* @param Data A data a ser analisada* @param Data Padrão Especifique formato* @return Data analisada Instância* / public Data estática Parsedate (data da string, padronização da string) {if (data == null) {throw new IllegalArgumentExceptException ("a data não deve ser n" "; } tente {return getsdf (padrão) .parse (data); } catch (parseException e) {e.printStackTrace (); Logger.error ("O formato analisado não suporta:"+padrão); } retornar nulo; } / *** Data de formato de acordo com o padrão especificado* @param data a data a ser formatada* @param padrão Especifique o formato* @return formato analisado* / public static string formatdate (data de data, padronização de string) {if (date == null) {throw new ilegalArgutumentException ("a data não deve ser Null"); } else {return getsdf (padron) .format (data); }}}teste
Execute um no tópico principal e os outros dois no tópico infantil, ambos usando o mesmo padrão
public static void main (string [] args) {dateutil.formatdate (new Date (), mdhms); novo thread (()-> {dateutil.formatdate (new Date (), mdhms);}). start (); novo thread (()-> {dateutil.formatdate (new Date (), mdhms);}). start (); }Análise de log
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: MMddHHmmssSSSS
analisar
Pode -se observar que o SDFMAP foi inserido uma vez, enquanto o SimpleDateFormat era novo três vezes porque havia três threads no código. Então, por que isso?
Para cada encadeamento, existe uma referência variável global ao ThreadLocal.ThreadLocalMap ThreadLocals. Existe um threadlocal.threadlocalmap que mantém o threadlocal e o valor correspondente. Uma imagem é melhor do que mil palavras. O diagrama de estrutura é o seguinte:
Então, para o sdfmap, o diagrama de estrutura será alterado
1. Primeiro execute dateutil.formatdate (new Date (), mdhms);
// A primeira vez que executei o dateUtil.FormatDate (new Date (), MDHMSS) estática privada SimpleDateFormat Getsdf (Final String Pattern) {Threadlocal <SimpledEfformat> sdfthread = sdfmap.get (padrão); // O sdfthread obtido é nulo, digite a instrução if if (sdfthread == null) {synchronized (dateUtil.class) {sdfThread = sdfmap.get (padrão); // sdfthread ainda é nulo, digite se a instrução if (sdfthread == null) {// imprima logger.debug ("coloque um novo sdf de padrão" + padrão + "para mapear"); // Crie a instância do ThreadLocal e substitua o método inicial do valor sdfthread = new Threadlocal <SimpledEfformat> () {@Override protegen SimpleDateFormat InitialValue () {Logger.debug ("Thread:" Thread.CurrentThread () + "Init Pattern:" + Pattern); retornar novo SimpleDateFormat (padrão); }}; // definido como sdfmap sdfmap.put (padrão, sdfthread); }}} return sdfthread.get (); } No momento, alguém pode perguntar, o método do conjunto Threadlocal não é chamado aqui, então como você define o valor a ser inserido?
Isso requer a implementação de 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 ("desmarcado") t resultado = (t) e.value; resultado de retorno; }} retornar setInitialValue (); }Ou seja, quando o valor não existir, o método setInitialValue () será chamado, que chamará o método InitialValue (), que é o método que substituímos.
Impressão de log correspondente.
Coloque um novo sdf de padrão mmddhhmmsssss para mapThread: thread [main, 5, main] init padrão: mmddhhmmsssss
2. Execute dateUtil.FormatDate (new Date (), MDHMSS) no encadeamento da criança pela segunda vez;
// execute `dateUtil.formatdate (new Date (), mdhms);` private static simpledateFormat Getsdf (padrão da sequência final) {threadlocal <pleledateFormat> sdfthread = sdfmap.get (padrão); // O sdfthread obtido aqui não é nulo, pule se bloco if (sdfthread == null) {synchronized (dateUtil.class) {sdfthread = sdfmap.get (padrão); if (sdfthread == null) {logger.debug ("Coloque um novo sdf de padrão" + padrão + "para mapear"); sdfthread = new Threadlocal <SimpledateFormat> () {@Override Protected SimpleDateFormat InitialValue () {Logger.debug ("Thread:" + Thread.currentThread () + "init padrão:" + padrão); retornar novo SimpleDateFormat (padrão); }}; sdfmap.put (padrão, sdfthread); }}} // ligue para sdfthread.get () diretamente para retornar sdfthread.get (); }Análise sdfthread.get ()
// execute `dateUtil.formatdate (new Date (), mdhms);` public t get () {thread t = thread.currentThread (); // obtenha o mapa atual do threadlocalmap threadlocalmap = getMap (t); // O mapa obtido no encadeamento filho é nulo, pule se bloco se (map! = Null) {threadlocalmap.entry e = map.getEntry (this); if (e! = null) {@suppresswarnings ("desmarcado") t resultado = (t) e.value; resultado de retorno; }} // Execute a inicialização diretamente, ou seja, ligue para o método InitialValue (), substituímos o retorno setInitialValue (); }Log correspondente:
Thread [Thread-1,5, Main] Padrão Init: Mmddhhmmsssss
Resumir
Em que cenário é mais adequado para o uso do Threadlocal? Alguém deu uma resposta muito boa no Stackoverflow.
Quando e como devo usar uma variável Threadlocal?
Um uso possível (e comum) é quando você tem algum objeto que não é seguro para threads, mas deseja evitar o acesso a esse objeto (estou olhando para você, SimpleDateFormat). Em vez disso, dê a cada tópico sua própria instância do objeto.
Código de referência:
https://github.com/nl101531/util-demo sob Javaweb
Referências:
Aprenda java threadlocal de uma maneira fácil de entender
Problema de segurança do tópico do SimpleDateFormat
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.