Este artículo ayuda a todos a comprender ThreadLocal mediante el uso de casos de optimización SimpleDateFormat que no son seguros en entornos concurrentes.
Recientemente, resolví los proyectos de la compañía y descubrí que hay muchas cosas malas escritas, como las siguientes:
clase pública dateutil {private final static simpledateFormat sdfyhm = new SimpleDateFormat ("yyyymmdd"); Public Sincronized Static Date ParseyMDHMS (fuente de cadena) {try {return sdfyhm.parse (fuente); } catch (ParseException e) {E.PrintStackTrace (); devolver nueva fecha (); }}} Primero, analicemos:
La función parseymdhms () en este punto utiliza modificación sincronizada, lo que significa que la operación es no austa, por lo que debe sincronizarse. El hilo unsafe solo puede ser el método parse () de SimpleDateFormat. Verifique el código fuente. Hay una variable global en SimpleDateFormat.
Calendario de calendario protegido; date parse () {calendar.clear (); ... // Realizar algunas operaciones para establecer la fecha del calendario y otros calendario.gettime (); // Obtenga el momento del calendario}La operación Clear () causará insegura de subprocesos.
Además, el uso de la palabra clave sincronizada tiene un gran impacto en el rendimiento, especialmente cuando el subproceso múltiple, cada vez que se llama el método ParseyMDHMS, se realizará el juicio de sincronización, y la sincronización en sí es muy costosa, por lo que esta es una solución irrazonable.
Método de mejora
La inseguanza de hilos es causada por el uso de variables compartidas por múltiples hilos, por lo que aquí usamos ThreadLocal <SimpedateFormat> para crear una variable de copia para cada hilo por separado. Primero administre el código y luego analice la causa de este problema para resolver el problema.
/** * La clase de herramienta de fecha (ThreadLocal se usa para obtener SimpleDateFormat, y otros métodos se pueden copiar directamente por Common-Lang) * @author niu li * @date 2016/11/19 */public class dateUtil {private static map <tring, threadlocal <simpleDeFormat>> sdfmap = new Hashmap <String <String <ShiflLocal <SimpledAteAtateTate> (SimpledAformat>> SDFMAP = New Hashmap <String <String <StringLocal <SimpledAteAtateTate> () () () () (); private static logger logger = loggerFactory.getLogger (dateUtil.class); Cadena estática final pública mdhmss = "mmddhhmmsssss"; Cadena estática final pública ymdhms = "yyymmddhhmmss"; Cadena estática final pública ymdhms_ = "yyyy-mm-dd hh: mm: ss"; Cadena estática final pública ymd = "yyyymmdd"; Cadena estática final pública ymd_ = "yyyy-mm-dd"; Cadena estática final pública HMS = "HHMMSS"; / *** Obtenga la instancia SDF del hilo correspondiente basado en la clave en la tecla MAP* @param Pattern en el mapa* @return esta instancia*/ private static simpledateFormat getsdf (patrón de cadena final) {ThreadLocal <SimpleDateFormat> sdfthread = sdfmap.get (patrón); if (sdfthread == null) {// verificación doble para evitar que SDFMAP ponga el valor en varias veces, y el motivo del doble bloqueo sincronizado es el mismo que sincronizado (dateUtil.class) {sdfthread = sdfmap.get (patrón); if (sdfthread == null) {logger.debug ("Ponga nuevo sdf del patrón" + patrón + "para mapear"); sdfthread = new ThreadLocal <SimpleDateFormat> () {@Override SimpledateFormat InitialValue () {logger.debug ("Thread:" + Thread.CurrentThread () + "Init Pattern:" + Pattern); devolver nuevo SimpleDateFormat (patrón); }}; sdfmap.put (patrón, sdfthread); }}} return sdfthread.get (); } / *** Poner la fecha de acuerdo con el patrón especificado* @param Fecha La fecha que se analizará* @param Fecha Patrón de especificación Formato* @@return } try {return getSdf (patrón) .parse (fecha); } catch (ParseException e) {E.PrintStackTrace (); logger.error ("El formato analizado no es compatible con:"+patrón); } return null; } / *** Fecha de formato Según el patrón especificado* @param Fecha de la fecha que se formatea* @param Patrón Especifique el formato* @return formato analizado* / public static string formatDate (fecha de fecha, patrón de cadena) {if (date == null) {tirar nueva IllegalArgumentException ("La fecha no debe ser nulo");; } else {return getSdf (patrón) .Format (fecha); }}}prueba
Ejecutar uno en el hilo principal y los otros dos en el hilo infantil, ambos usando el mismo patrón
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 (); }Análisis de registro
Ponga el nuevo SDF del patrón mmddhhmmmsssss para mapthread: hilo [principal, 5, principal] patrón de init: mmddhhmmsssssthread: hilt [hilo-0,5, main] init patrones: mmddhmmssssssthread: hilo [hilo -,5, main] init: mmdddhhmmsssssssssssssssssssssssssssssssssssssssssssshread
analizar
Se puede ver que SDFMAP put ingresado una vez, mientras que SimpleDateFormat fue nuevo tres veces porque había tres hilos en el código. Entonces, ¿por qué es esto?
Para cada subproceso, hay una referencia variable global a ThreadLocal.ThreadLocalMap ThreadLocals. Hay un ThreadLocal.ThreadLocalMap que contiene el valor de ThreadLocal y el valor correspondiente. Una imagen es mejor que mil palabras. El diagrama de la estructura es el siguiente:
Entonces, para SDFMAP, el diagrama de la estructura se cambiará
1. Primero ejecutar dateUtil.FormatDate (nueva fecha (), mdhmss);
// La primera vez que ejecuté DateUtil.FormatDate (New Date (), MDHMSS) Análisis privado static simpledateFormat getSdf (patrón de cadena final) {ThreadLocal <SimpedateFormat> sdfthread = sdfmap.get (patrón); // El sdfthread obtenido es nulo, ingrese si la instrucción if (sdfthread == null) {sincronizado (dateUtil.class) {sdfthread = sdfmap.get (patrón); // sdfthread todavía es nulo, ingrese if if Declary if (sdfthread == null) {// imprime logger.debug ("Ponga nuevo sdf de patrón" + patrón + "para mapear"); // Crear instancia de ThreadLocal y anular el método InitialValue sdfthread = new ThreadLocal <SimpleDateFormat> () {@Override Protected SimpleDateFormat InitialValue () {logger.debug ("Thread:" + Thread.CurrentThread () + "Patrón inicial:" + Pattern); devolver nuevo SimpleDateFormat (patrón); }}; // establecer en sdfmap sdfmap.put (patrón, sdfthread); }}} return sdfthread.get (); } En este momento, alguien puede preguntar, el método de establecimiento de ThreadLocal no se llama aquí, entonces, ¿cómo establece el valor para ingresar?
Esto requiere la implementación 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) {@suppleswarnings ("sin verificar") t resultado = (t) e.value; resultado de retorno; }} return setInitialValue (); }Es decir, cuando el valor no existe, se llamará al método SetInitialValue (), que llamará al método InitialValue (), que es el método que anulamos.
Impresión de registro correspondiente.
Ponga el nuevo SDF del patrón mmddhhmmsssss en mapthread: hilo [principal, 5, principal] patrón de init: mmddhmmsssss
2. Ejecutar dateUtil.FormatDate (nueva fecha (), MDHMSS) en el hilo infantil la segunda vez;
// ejecutar `dateUtil.FormatDate (new Date (), mdhmss);` private static simpledateFormat getSdf (patrón de cadena final) {ThreadLocal <SimpedateFormat> sdfthread = sdfmap.get (patrón); // El sdfthread obtenido aquí no es nulo, omita si bloquea si (sdfthread == null) {sincronizado (dateUtil.class) {sdfthread = sdfmap.get (patrón); if (sdfthread == null) {logger.debug ("Ponga nuevo sdf del patrón" + patrón + "para mapear"); sdfthread = new ThreadLocal <SimpleDateFormat> () {@Override SimpledateFormat InitialValue () {logger.debug ("Thread:" + Thread.CurrentThread () + "Init Pattern:" + Pattern); devolver nuevo SimpleDateFormat (patrón); }}; sdfmap.put (patrón, sdfthread); }}} // llamar sdfthread.get () directamente para devolver sdfthread.get (); }Análisis sdfthread.get ()
// ejecutar `dateUtil.FormatDate (new Date (), mdhmss);` public t get () {Thread t = Thread.CurrentThread (); // Obtener el hilo infantil actual MAPLOCALMAP MAP = getMap (t); // El mapa obtenido en el hilo infantil es nulo, omita si bloquee si (map! = Null) {threadlocalmap.entry e = map.getEntry (this); if (e! = null) {@suppleswarnings ("sin verificar") t resultado = (t) e.value; resultado de retorno; }} // Ejecutar la inicialización directamente, es decir, llamar al método InitialValue () que sobrescribimos SetInitialValue (); }Registro correspondiente:
Patrón de inicio de hilo [hilo-1,5, principal]: mmddhhmmsssss
Resumir
¿En qué escenario es más adecuado para usar ThreadLocal? Alguien dio una muy buena respuesta en Stackoverflow.
¿Cuándo y cómo debo usar una variable de ThreadLocal?
Un uso posible (y común) es cuando tiene algún objeto que no es seguro de subprocesos, pero desea evitar sincronizar el acceso a ese objeto (lo estoy mirando, SimpleDateFormat). En cambio, dé a cada hilo su propia instancia del objeto.
Código de referencia:
https://github.com/nl101531/util-demo bajo Javaweb
Referencias:
Aprenda Java ThreadLocal de una manera fácil de entender
El problema y la solución de seguridad de los hilos de SimpledateFormat
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.