simple - ¿Por qué todos los objetos Java tienen esperar() y notificar() y esto causa un impacto en el rendimiento?
reflection java 8 (4)
Cada Object
Java tiene los métodos wait()
y notify()
(y variantes adicionales). Nunca he usado estos y sospecho que muchos otros no lo han hecho. ¿Por qué son tan fundamentales que cada objeto tiene que tenerlos y hay un impacto en el rendimiento al tenerlos (presumiblemente se almacena algún estado en ellos)?
EDITAR para enfatizar la pregunta. Si tengo una List<Double>
con 100,000 elementos, cada Double
tiene estos métodos, ya que se extiende desde Object
. Pero parece poco probable que todos estos tengan que saber sobre los hilos que administran la List
.
EDITAR respuestas excelentes y útiles. @Jon tiene una muy buena publicación en el blog que cristalizó mis sentimientos viscerales. También estoy completamente de acuerdo con @Bob_Cross en que debería mostrar un problema de rendimiento antes de preocuparse por ello. (También como la enésima ley de los idiomas exitosos si hubiera sido un éxito de rendimiento, entonces Sun o alguien lo habría solucionado).
¿Por qué son tan fundamentales que cada objeto tiene que tenerlos y hay un impacto en el rendimiento al tenerlos (presumiblemente se almacena algún estado en ellos)?
tl; dr: Son métodos de seguridad de subprocesos y tienen costos pequeños en relación con su valor.
Las realidades fundamentales que soportan estos métodos son que:
- Java es siempre multiproceso. Ejemplo: consulte la lista de subprocesos utilizados por un proceso que utiliza jconsole o jvisualvm en algún momento.
- La corrección es más importante que el "rendimiento". Cuando estaba calificando proyectos (hace muchos años), solía tener que explicar que "llegar a la respuesta equivocada realmente rápido sigue siendo incorrecto".
Fundamentalmente, estos métodos proporcionan algunos de los ganchos para administrar los monitores por objeto utilizados en la sincronización. Específicamente, si me he synchronized(objectWithMonitor)
en un método en particular, puedo usar objectWithMonitor.wait()
para obtener ese monitor (por ejemplo, si necesito otro método para completar un cálculo antes de poder continuar). En ese caso, eso permitirá otro método que fue bloqueado esperando a que ese monitor continúe.
Por otro lado, puedo usar objectWithMonitor.notifyAll()
para que los hilos que están a la espera del monitor sepan que pronto voy a entregar el monitor. Sin embargo, no pueden continuar hasta que salga del bloque sincronizado.
Con respecto a ejemplos específicos (por ejemplo, largas listas de dobles) en los que podría preocuparse de que haya un impacto de memoria o rendimiento en el mecanismo de monitoreo, aquí hay algunos puntos que probablemente debería considerar:
- Primero, demuéstralo. Si crees que hay un gran impacto de un mecanismo central de Java como la corrección de múltiples subprocesos, hay una excelente posibilidad de que tu intuición sea falsa. Medir el impacto primero. Si es serio y sabes que nunca necesitarás sincronizar en un doble individual, considera usar dobles en su lugar.
- Si no está seguro de que usted, su compañero de trabajo, un futuro programador de mantenimiento (que podría ser usted mismo un año más tarde), etc., nunca jamás necesitará una granularidad fina de acceso a sus datos, existe una excelente oportunidad que quitar estos monitores solo haría que su código sea menos flexible y fácil de mantener.
Seguimiento en respuesta a la pregunta sobre objetos por objeto frente a objetos explícitos del monitor:
Respuesta corta: @JonSkeet: sí, eliminar los monitores crearía problemas: crearía fricción. Mantener esos monitores en Object
nos recuerda que este es siempre un sistema multiproceso.
Los monitores de objetos incorporados no son sofisticados pero son: fáciles de explicar; trabajar de manera predecible; y son claros en su propósito. synchronized(this)
es una clara declaración de intenciones. Si obligamos a los programadores novatos a utilizar el paquete de concurrencia exclusivamente, introducimos fricción. ¿Qué hay en ese paquete? ¿Qué es un semáforo? Tenedor de unirse?
Un codificador novato puede usar los monitores de objetos para escribir un código decente de controlador de vista de modelo. synchronized
, wait
y notifyAll
se puede usar notifyAll
para implementar una seguridad de subprocesos ingenua (en el sentido de simple, accesible pero quizás no vanguardista). El ejemplo canónico sería uno de estos Dobles (postulado por el OP) que puede tener un valor establecido por un hilo mientras que el hilo AWT obtiene el valor para ponerlo en un JLabel. En ese caso, no hay una buena razón para crear un Objeto adicional explícito solo para tener un monitor externo.
En un nivel ligeramente más alto de complejidad, estos mismos métodos son útiles como un método de monitoreo externo. En el ejemplo anterior, lo hice explícitamente (vea los fragmentos de objectWithMonitor arriba). Nuevamente, estos métodos son realmente útiles para armar una seguridad de hilos relativamente simple.
Si desea ser aún más sofisticado, creo que debería pensar seriamente en leer Java Concurrency In Practice (si aún no lo ha hecho). Los bloqueos de lectura y escritura son muy poderosos sin agregar demasiada complejidad adicional.
Punchline: utilizando métodos básicos de sincronización, puede explotar una gran parte del rendimiento habilitado por los modernos procesadores de múltiples núcleos con seguridad de subprocesos y sin mucha sobrecarga.
Bueno, sí significa que cada objeto debe tener un monitor asociado. El mismo monitor se utiliza para synchronized
. Si está de acuerdo con la decisión de poder sincronizar con cualquier objeto, wait()
y notify()
no agregue más estados por objeto. La JVM puede asignar el monitor real perezosamente (sé que .NET lo hace) pero tiene que haber algo de espacio de almacenamiento disponible para indicar qué monitor está asociado con el objeto. Admito que es posible que esta sea una cantidad muy pequeña (por ejemplo, 3 bytes) que en realidad no ahorraría ningún tipo de memoria debido al relleno del resto de la sobrecarga del objeto. Tendría que ver cómo cada JVM manejó la memoria para decir. sin lugar a duda.
Tenga en cuenta que el solo hecho de tener métodos adicionales no afecta el rendimiento (aparte de muy poco debido a que el código obvio está presente en algún lugar ). No es como cada objeto o incluso cada tipo tiene su propia copia del código para wait()
y notify()
. Dependiendo de cómo funcionen los vtables, cada tipo puede terminar con una entrada adicional para cada método heredado, pero eso es solo por tipo, no por objeto. Básicamente, se perderá en el ruido en comparación con la mayor parte del almacenamiento, que es para los objetos en sí.
Personalmente, siento que tanto .NET como Java cometieron un error al asociar un monitor con cada objeto, prefiero tener objetos de sincronización explícitos. Escribí un poco más sobre esto en una publicación de blog sobre el rediseño de java.lang.Object / System.Object .
Estos métodos están alrededor para implementar la comunicación entre subprocesos.
Revisa este artículo sobre el tema .
Reglas para esos métodos, tomadas de ese artículo:
- wait () le dice al subproceso que llama que renuncie al monitor y se ponga en suspensión hasta que otro subproceso ingrese en el mismo monitor y las llamadas notifiquen ().
- notificar () despierta el primer hilo que llamó a espera () en el mismo objeto.
- notifyAll () activa todos los subprocesos que llamaron a wait () en el mismo objeto. El hilo de mayor prioridad se ejecutará primero.
Espero que esto ayude...
Todos los objetos en Java tienen monitores asociados con ellos. Las primitivas de sincronización son útiles en casi todos los códigos de subprocesos múltiples, y es muy agradable de sincronizar semánticamente en los objetos a los que está accediendo en lugar de en objetos "Monitor" separados.
Java puede asignar los monitores asociados con los objetos según sea necesario, como lo hace .NET, y en cualquier caso, la sobrecarga real para simplemente asignar (pero no usar) el bloqueo sería bastante pequeña.
En resumen: es realmente conveniente almacenar Objetos con sus bits de soporte de seguridad de subprocesos, y hay muy poco impacto en el rendimiento.