recolector qué garbagecollector funcionamiento ejemplo collector collection basura java concurrency threadpool thread-local

qué - recolector de basura java ejemplo



¿Es realmente mi trabajo limpiar los recursos de ThreadLocal cuando las clases se han expuesto a un grupo de subprocesos? (5)

Mi uso de ThreadLocal

En mis clases de Java, a veces uso un ThreadLocal principalmente para evitar la creación innecesaria de objetos:

@net.jcip.annotations.ThreadSafe public class DateSensitiveThing { private final Date then; public DateSensitiveThing(Date then) { this.then = then; } private static final ThreadLocal<Calendar> threadCal = new ThreadLocal<Calendar>() { @Override protected Calendar initialValue() { return new GregorianCalendar(); } }; public Date doCalc(int n) { Calendar c = threadCal.get(); c.setTime(this.then): // use n to mutate c return c.getTime(); } }

Hago esto por la razón adecuada: GregorianCalendar es uno de esos objetos inmutables, mutables, sin hilos, que ofrecen un servicio en múltiples llamadas, en lugar de representar un valor. Además, se considera que es "costoso" crear una instancia (si esto es cierto o no no es el punto de esta pregunta). (En general, realmente lo admiro :-))

Cómo se queja Tomcat

Sin embargo, si uso una clase de este tipo en cualquier entorno en el que se agrupan subprocesos ( y donde mi aplicación no controla el ciclo de vida de esos subprocesos), existe la posibilidad de fugas de memoria. Un entorno Servlet es un buen ejemplo.

De hecho, Tomcat 7 se queja cuando se detiene una aplicación web:

GRAVE: la aplicación web [] creó un ThreadLocal con la clave de tipo [org.apache.xmlbeans.impl.store.CharUtil $ 1] (valor [org.apache.xmlbeans.impl.store.CharUtil$1@2aace7a7]) y un valor de tipo [java.lang.ref.SoftReference] (valor [java.lang.ref.SoftReference@3d9c9ad4]) pero no pudo eliminarlo cuando se detuvo la aplicación web. Los hilos se renovarán con el tiempo para intentar evitar una posible pérdida de memoria. 13 de diciembre, 2012 12:54:30 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks

(Ni siquiera mi código lo hace, en ese caso particular).

¿A quién culpar?

Esto apenas parece justo. Tomcat me está culpando (o al usuario de mi clase) por hacer lo correcto.

En última instancia, eso es porque Tomcat quiere reutilizar los hilos que me ofreció, para otras aplicaciones web. (Ugh, me siento sucio.) Probablemente, no es una buena política por parte de Tomcat, porque los subprocesos realmente tienen / causan estado, no los compartan entre aplicaciones.

Sin embargo, esta política es al menos común, incluso si no es deseable. Siento que estoy obligado (como usuario de ThreadLocal a proporcionar una forma para que mi clase "libere" los recursos que mi clase ha adjuntado a varios subprocesos.

¿Pero que se puede hacer al respecto?

¿Qué es lo correcto para hacer aquí?

Para mí, parece que la política de reutilización de subprocesos del motor del servlet está en desacuerdo con la intención detrás de ThreadLocal .

Pero tal vez debería proporcionar una facilidad para permitir que los usuarios digan "paso, estado específico del subproceso malvado asociado con esta clase, aunque no estoy en posición de dejar que el hilo muera y dejar que GC haga su trabajo". ¿Es posible para mí hacer esto? Quiero decir, no es como si pudiera hacer que ThreadLocal#remove() sea ​​llamado en cada uno de los Threads que vieron ThreadLocal#initialValue() en algún momento en el pasado. ¿O hay otra manera?

¿O debería simplemente decirle a mis usuarios "vaya y consiga un cargador de clases decente y una implementación de agrupación de subprocesos"?

EDITAR # 1 : aclaró cómo se usa threadCal en una clase de utilidad vanailla que desconoce los ciclos de vida de los hilos EDIT # 2 : Se DateSensitiveThing un problema de seguridad de los hilos en DateSensitiveThing


Suspiro, esto es noticia vieja

Bueno, un poco tarde para la fiesta en este caso. En octubre de 2007, Josh Bloch (coautor de java.lang.ThreadLocal junto con Doug Lea) wrote :

"El uso de grupos de subprocesos exige un cuidado extremo. El uso descuidado de los grupos de subprocesos en combinación con el uso descuidado de los locales de subprocesos puede provocar la retención involuntaria de objetos, como se ha observado en muchos lugares".

La gente se quejaba de la mala interacción de ThreadLocal con los grupos de subprocesos incluso en ese momento. Pero Josh sancionó:

"Instancias por hilo para el rendimiento. El ejemplo de Aaron''s SimpleDateFormat (arriba) es un ejemplo de este patrón".

Algunas lecciones

  1. Si coloca cualquier tipo de objetos en cualquier grupo de objetos, debe proporcionar una forma de eliminarlos ''más adelante''.
  2. Si se '' ThreadLocal '' usando un ThreadLocal , tiene opciones limitadas para hacerlo. O bien: a) sabe que el / los Thread (s) donde usted pone los valores terminarán cuando su aplicación finalice; O b) luego puede organizar el mismo hilo que invocó ThreadLocal # set () para invocar ThreadLocal # remove () cada vez que su aplicación finalice
  3. Como tal, su uso de ThreadLocal como grupo de objetos exigirá un alto precio en el diseño de su aplicación y su clase. Los beneficios no vienen gratis.
  4. Como tal, el uso de ThreadLocal es probablemente una optimización prematura, a pesar de que Joshua Bloch lo instó a que lo considere en ''Java efectiva''.

En resumen, la decisión de utilizar un ThreadLocal como una forma de acceso rápido e incontrolado a "grupos de instancias por subproceso" no es una decisión que deba tomarse a la ligera.

NOTA: Hay otros usos de ThreadLocal que no sean ''grupos de objetos'', y estas lecciones no se aplican a aquellos escenarios en los que ThreadLocal solo se debe configurar de forma temporal de todos modos, o donde existe un estado real por hilo para mantener pista de.

Consecuencias para los implementadores de la biblioteca

Existen algunas consecuencias para los implementadores de bibliotecas (incluso cuando dichas bibliotecas son clases de utilidad simples en su proyecto).

Ya sea:

  1. Utiliza ThreadLocal, consciente de que podría "contaminar" los hilos de larga duración con equipaje adicional. Si está implementando java.util.concurrent.ThreadLocalRandom , podría ser apropiado. (Es posible que Tomcat aún se moleste con los usuarios de su biblioteca, si no está implementando en java.* ). Es interesante observar la disciplina con la que java.* Hace un uso moderado de la técnica ThreadLocal.

O

  1. Usted usa ThreadLocal y le da a los clientes de su clase / paquete: a) la oportunidad de elegir renunciar a esa optimización ("no use ThreadLocal ... No puedo organizar la limpieza"); Y b) una forma de limpiar los recursos de ThreadLocal ("está bien usar ThreadLocal ... Puedo organizar que todos los subprocesos que usaron para invocar LibClass.releaseThreadLocalsForThread() cuando termine con ellos.

Sin embargo, hace que su biblioteca sea "difícil de usar correctamente".

O

  1. Le da a sus clientes la oportunidad de suministrar su propia mejora de grupo de objetos (que podría usar ThreadLocal, o sincronización de algún tipo). ("OK, puedo darte un new ExpensiveObjectFactory<T>() { public T get() {...} } si crees que es realmente necesario".

No es tan malo. Si el objeto es realmente tan importante y costoso de crear, la agrupación explícita probablemente valga la pena.

O

  1. Usted decide que de todos modos no vale tanto para su aplicación y encuentra una manera diferente de abordar el problema. Esos objetos que son costosos de crear, mutables y que no son seguros para subprocesos le están causando dolor ... ¿los está usando realmente como la mejor opción?

Alternativas

  1. Agrupación regular de objetos, con toda su sincronización contendida.
  2. No agrupar objetos: solo cree una instancia en un ámbito local y descártelos más tarde.
  3. No agrupar subprocesos (a menos que pueda programar un código de limpieza cuando lo desee), no use sus cosas en un contenedor JaveEE
  4. Grupos de subprocesos que son lo suficientemente inteligentes como para limpiar ThreadLocals sin quejarse de ti.
  5. Grupos de subprocesos que asignan subprocesos en una base ''por aplicación'', y luego los dejan morir cuando se detiene la aplicación.
  6. Un protocolo entre contenedores de agrupación de subprocesos y aplicaciones que permitía el registro de un "controlador de cierre de aplicación", que el contenedor podía programar para ejecutarse en subprocesos que se habían utilizado para dar servicio a la aplicación ... en algún momento en el futuro, cuando ese subproceso estaba siguiente disponible P.ej. servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})

Sería bueno ver cierta estandarización en los últimos 3 elementos, en futuras especificaciones de JavaEE.

Nota de inicio

En realidad, la creación de instancias de un GregorianCalendar es bastante ligera. Es la llamada inevitable a setTime() que incurre en la mayor parte del trabajo. Tampoco tiene ningún estado significativo entre diferentes puntos de la ejecución de un hilo. Es poco probable que poner un Calendar en un ThreadLocal le devuelva más de lo que le cuesta ... a menos que el perfil muestre un punto caliente en el new GregorianCalendar() .

new SimpleDateFormat(String) es costoso en comparación, porque tiene que analizar la cadena de formato. Una vez analizado, el "estado" del objeto es importante para los usos posteriores del mismo hilo. Es un mejor ajuste. Pero aún podría ser "menos costoso" crear una instancia nueva, en lugar de otorgarle a sus clases responsabilidades adicionales.


Creo que ThreadPoolExecutor de JDK podría realizar la limpieza de ThreadLocals después de la ejecución de la tarea, pero como sabemos, no lo hace. Creo que podría haber proporcionado al menos una opción. El motivo puede ser que Thread proporciona solo paquetes de acceso privado a sus mapas TreadLocal, por lo que ThreadPoolExecutor simplemente no puede acceder a ellos sin cambiar la API de Thread.

Curiosamente, ThreadPoolExecutor ha protegido los apéndices de métodos antes de beforeExecution y después de afterExecution , la API dice: These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals... These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals... Así que puedo imaginar una tarea que implementa una interfaz ThreadLocalCleaner y nuestro ThreadPoolExecutor personalizado que en afterExecution llama a la tarea cleanThreadLocals () de la tarea;


Después de pensar en esto durante un año, decidí que no es aceptable que un contenedor JavaEE comparta hilos de trabajo agrupados entre instancias de aplicaciones no relacionadas. Esto no es "empresa" en absoluto.

Si realmente va a compartir subprocesos, java.lang.Thread (en un entorno JavaEE, al menos) debería admitir métodos como setContextState(int key) y forgetContextState(int key) (reflejo de setClasLoaderContext() ), que permiten contenedor para aislar el estado ThreadLocal específico de la aplicación, ya que entrega el hilo entre varias aplicaciones.

A la espera de tales modificaciones en el espacio de nombres java.lang , solo es razonable que los implementadores de aplicaciones adopten una regla de ''un conjunto de subprocesos, una instancia de aplicaciones relacionadas'', y que los desarrolladores de aplicaciones asuman que ''este subproceso es mío hasta que ThreadDeath lo haga. parte''.


Si es útil, uso un SPI personalizado (una interfaz) y el JDK ServiceLoader . Luego, todas mis varias bibliotecas internas (archivos jar) que necesitan realizar la descarga de threadlocals simplemente siguen el patrón de ServiceLoader. Entonces, si un frasco necesita una limpieza threadlocal, se seleccionará automáticamente si tiene el /META-INF/services/interface.name adecuado.

Luego hago la descarga en un filtro u oyente (tuve algunos problemas con los oyentes, pero no puedo recordar qué).

Sería ideal si el JDK / JEE viniera con un SPI estándar para borrar los threadlocals.


Ya que usted no creó el hilo, solo lo alquiló usted, creo que es justo exigir que lo limpie antes de dejar de usarlo, justo cuando llena el tanque de un automóvil alquilado cuando regresa. Tomcat podría limpiar todo por sí mismo, pero te hace un favor, recordándote que olvidaste las cosas.

AGREGAR: la forma en que usa el GregorianCalendar preparado es simplemente errónea: como las solicitudes de servicio pueden ser concurrentes y no hay sincronización, doCalc puede tomar getTime setTime invocado por otra solicitud. La introducción de la sincronización haría las cosas más lentas, por lo que crear una nueva GregorianCalendar podría ser una mejor opción.

En otras palabras, su pregunta debe ser: cómo mantener el conjunto de instancias de GregorianCalendar preparadas para que su número se ajuste a la tasa de solicitud. Por lo tanto, como mínimo, necesita un singleton que contenga ese grupo. Cada contenedor Ioc tiene medios para administrar un singleton, y la mayoría tiene implementaciones listas de objetos listas. Si aún no usa un contenedor IoC, comience a usar uno (String, Guice), en lugar de reinventar la rueda.