java - lock - ¿Es posible que un hilo se bloquee?
swingworker java ejemplo (18)
¿Es técnicamente posible que un hilo en Java se estanquee?
Me lo preguntaron en una entrevista hace un tiempo y respondí que no era posible, pero el entrevistador me dijo que sí. Lamentablemente, no pude obtener su método sobre cómo lograr este punto muerto.
Esto me hizo pensar y la única situación en la que puedo pensar es donde puedes hacer que esto suceda es donde tienes un proceso de servidor RMI que contenía un método que se llama a sí mismo. La línea de código que llama al método se coloca en un bloque sincronizado.
¿Es eso posible o el entrevistador fue incorrecto?
El código fuente que estaba pensando estaba en esta línea (donde testDeadlock se ejecuta en un proceso de servidor RMI)
public boolean testDeadlock () throws RemoteException {
synchronized (this) {
//Call testDeadlock via RMI loopback
}
}
Aquí hay una forma de que un hilo se estanque.
public class DeadlockMe
{
public static void main(String[] args)
{
DeadlockThing foo = new DeadlockThing();
synchronized(foo)
{
try
{
foo.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
El hilo crea una instancia de una clase, cualquier clase y espera en ella. Como el hilo creó un objeto con alcance local, no hay forma posible de que ningún otro hilo notifique al objeto para activar el hilo.
Bueno, basado en la definición de:
Un punto muerto es una situación donde dos o más acciones competidoras están esperando que el otro termine.
Yo diría que la respuesta es no, seguro de que un hilo puede sentarse allí esperando indefinidamente por algo, sin embargo, a menos que dos acciones en competencia se esperen entre sí, por definición no es un punto muerto.
A menos que alguien me explique cómo un solo hilo puede estar esperando simultáneamente a que finalicen dos acciones.
ACTUALIZACIÓN: La única situación posible en la que puedo pensar es algún tipo de bomba de mensajes, donde un hilo procesa un mensaje que le pide que espere indefinidamente para que algo suceda, donde de hecho ese algo será procesado por otro mensaje en el mensaje bomba .
Este escenario (increíblemente artificial) podría ser técnicamente llamado punto muerto.
Cuando un hilo entra en el bloque sincronizado, comprueba si el hilo actual es el propietario del bloqueo, y si lo está, el hilo simplemente continúa sin esperar.
Entonces no creo que sea posible.
Depende de lo que quiere decir con "punto muerto" exactamente. Por ejemplo, podría wait()
fácilmente en un monitor que nada volvería a pulsar ... pero no creo que llame a ese punto muerto, como tal.
Pensando en sus líneas de "método que se llama a sí mismo", si su servidor solo ejecutó un cierto número de subprocesos, todos podrían estar ocupados esperando respuestas de un mismo servidor, si eso cuenta. (El ejemplo más simple: el servidor solo usa un hilo para procesar. Si escribe un controlador de solicitud que llama al mismo servidor, estará esperando que el hilo bloqueado termine de manejar la solicitud antes de que pueda servir la misma solicitud ...) Este no es realmente un bloqueo de "bloque sincronizado", pero ciertamente es un peligro a tener en cuenta.
EDITAR: para aplicar esta respuesta a la definición en los otros, las acciones competidoras aquí serían "completar la solicitud actual" y "manejar la nueva solicitud". Cada acción está esperando que ocurra la otra.
El entrevistador tenía razón. Un hilo puede bloquearse a sí mismo de acuerdo con JCIP . ¿Pero cómo?
En la sección 2.3.2 del JCIP, tenemos el siguiente párrafo sobre reentrada:
La reentrada facilita la encapsulación del comportamiento de bloqueo y, por lo tanto, simplifica el desarrollo de código concurrente orientado a objetos. Sin reentrañas, el código de aspecto muy natural en el Listado 2.7, en el que una subclase anula un método sincronizado y luego llama al método de superclase, bloqueará.
El bloqueo de la palabra clave sincronizada es un bloqueo de reentrada, por lo que un hilo puede bloquearse y desbloquearse de forma anidada, pero si utiliza un bloqueo sin reentrada como el siguiente ejemplo, lo escribí como prueba. ¡Tendrás un punto muerto! De acuerdo con JCIP.
public class SelfDeadLock {
public static class Father{
volatile protected int n = 0;
protected Lock ourLock = new Lock();
public void writeSth(){
try {
ourLock.lock();
n++;
System.out.println("Father class: " + n);
} catch (InterruptedException ex) {
Logger.getLogger(SelfDeadLock.class.getName()).log(Level.SEVERE, null, ex);
}
ourLock.unlock();
}
}
public static class Child extends Father{
@Override
public void writeSth() {
try {
ourLock.lock();
n++;
System.out.println("Child class: " + n);
super.writeSth();
} catch (InterruptedException ex) {
Logger.getLogger(SelfDeadLock.class.getName()).log(Level.SEVERE, null, ex);
}
ourLock.unlock();
}
}
public static void main(String[] args) {
Child child = new Child();
child.writeSth();
}
}
La JVM solo realiza un seguimiento del hilo local que tiene el monitor, si la clase llamante realiza una llamada externa nuevamente, la llamada entrante causa que el hilo original se estanque.
Debería poder ejecutar este código para ilustrar la idea
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.*;
public class DeadlockThreadExample {
public static interface DeadlockClass extends Remote {
public void execute() throws RemoteException;
}
public static class DeadlockClassImpl extends UnicastRemoteObject implements DeadlockClass {
private Object lock = new Object();
public DeadlockClassImpl() throws RemoteException {
super();
}
public void execute() throws RemoteException {
try {
System.out.println("execute()::start");
synchronized (lock) {
System.out.println("execute()::Entered Lock");
DeadlockClass deadlockClass = (DeadlockClass) Naming.lookup("rmi://localhost/DeadlockClass");
deadlockClass.execute();
}
System.out.println("execute()::Exited Lock");
} catch (NotBoundException e) {
System.out.println(e.getMessage());
} catch (java.net.MalformedURLException e) {
System.out.println(e.getMessage());
}
System.out.println("execute()::end");
}
}
public static void main(String[] args) throws Exception {
LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
DeadlockClassImpl deadlockClassImpl = new DeadlockClassImpl();
Naming.rebind("DeadlockClass", deadlockClassImpl);
DeadlockClass deadlockClass = (DeadlockClass) Naming.lookup("rmi://localhost/DeadlockClass");
deadlockClass.execute();
System.exit(0);
}
}
La salida del programa se parece a
execute()::start
execute()::Entered Lock
execute()::start
Además, el subproceso también volcado muestra el siguiente
"main" prio=6 tid=0x00037fb8 nid=0xb80 runnable [0x0007f000..0x0007fc3c]
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
at java.io.BufferedInputStream.read(BufferedInputStream.java:235)
- locked <0x02fdc568> (a java.io.BufferedInputStream)
at java.io.DataInputStream.readByte(DataInputStream.java:241)
"RMI TCP Connection(4)-172.17.23.165" daemon prio=6 tid=0x0ad83d30 nid=0x1590 waiting for monitor entry [0x0b3cf000..0x0b3cfce8]
at DeadlockThreadExample$DeadlockClassImpl.execute(DeadlockThreadExample.java:24)
- waiting to lock <0x0300a848> (a java.lang.Object)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
"RMI TCP Connection(2)-172.17.23.165" daemon prio=6 tid=0x0ad74008 nid=0x15f0 runnable [0x0b24f000..0x0b24fbe8]
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
at java.io.BufferedInputStream.read(BufferedInputStream.java:235)
- locked <0x02ffb6d8> (a java.io.BufferedInputStream)
at java.io.DataInputStream.readByte(DataInputStream.java:241)
lo que indica que el hilo de hecho ha logrado bloquearse
La actualización de un bloqueo de lectura a un bloqueo de escritura (tratando de adquirir un bloqueo de escritura mientras se mantiene un bloqueo de lectura) hará que el hilo quede completamente bloqueado. ¿Es eso un punto muerto? Tú eres el juez ... Pero esa es la forma más fácil de crear el efecto con un solo hilo.
http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html
La respuesta (Pram''s) marcada como correcta no es técnicamente un punto muerto como otros han sugerido. Está bloqueado.
Sugeriría en Java que puede apoyarse en la definición de Java (que es consistente con la idea de dos hilos). El último juez entonces puede ser la JVM si detecta el punto muerto mismo . Entonces, en el ejemplo de Pram, el hilo mostraría algo como lo siguiente si se tratara de un punto muerto geniune.
Deadlock detected
=================
"Negotiator-Thread-1":
waiting to lock Monitor of com.google.code.tempusfugit.concurrency.DeadlockDetectorTest$Cat@ce4a8a
which is held by "Kidnapper-Thread-0"
"Kidnapper-Thread-0":
waiting to lock Monitor of com.google.code.tempusfugit.concurrency.DeadlockDetectorTest$Cash@7fc8b2
which is held by "Negotiator-Thread-1"
Esta detección de interbloqueo está disponible para bloqueos intrínsecos desde 1.5 y bloqueos cíclicos bloqueados desde 1.6.
Un recurso continuamente bloqueado, o al menos algo que está esperando algo que nunca sucederá se llama un bloqueo en vivo . Problemas similares donde los procesos fuera de la VM (por ejemplo) el bloqueo de bases de datos son completamente posibles, pero yo diría que no es apropiado para la pregunta.
Me interesaría escribir sobre cómo el entrevistador dice que es posible ...
En respuesta a su pregunta original, se necesitan dos para bailar tango y yo sugeriría que la respuesta de Pram no debería marcarse como correcta porque ¡no lo es! ;) El subproceso RMI que devuelve la llamada puede causar el bloqueo, pero se ejecuta en un hilo diferente (administrado por el servidor RMI) que el de principal. Dos hilos están involucrados incluso si el hilo principal no configuró explícitamente otro. No hay punto muerto como lo demuestra la falta de detección en el volcado de subprocesos (o si hace clic en ''detectar interbloqueo'' en jconsole), sería descrito con más precisión como un bloqueo en vivo.
Habiendo dicho todo eso, cualquier discusión a lo largo de las líneas de cada una de estas respuestas sería suficiente para satisfacerme como entrevistador.
Lo ideal sería que un hilo nunca creara un punto muerto usando ''bloqueos sincronizados'' a menos que realmente haya un error en la JVM como ''presuntamente'' notado por algunas personas en versiones anteriores
No, porque Java implementa la reentrada . Pero, por favor, no mezcle la concurrencia y la RMI así. La sincronización en los stubs es algo completamente diferente a los objetos remotos que están sincronizados internamente.
Puedes http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html a un solo hilo Deadlock con http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html . Los bloqueos de escritura pueden adquirir bloqueos de lectura, pero no al revés. Lo siguiente se bloqueará indefinidamente.
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
lock.writeLock().lock();
Quizás lo que el entrevistador estaba pensando era:
Thread.currentThread().join();
Sin embargo, yo diría que no cuenta como un punto muerto.
Quizás se refería a LOCK en sí mismo, eso es demasiado fácil:
synchronized( this )
{
wait( );
}
Sé que esta es una publicación anterior. Aquí hay otro ejemplo de cómo podría suceder si su código tiene interacción con recursos externos:
Tengo un hilo que abre una conexión de base de datos, inicia una transacción A y comienza la actualización. El mismo hilo, abra otra conexión, inicie otra transacción B. Sin embargo, debido a que transactionA no se ha comprometido todavía, y tiene la tabla de la base de datos bloqueada, la transacción B sucede para acceder a esta tabla bloqueada, por lo que debe esperar
Al final, el mismo hilo es bloque en sí mismo porque abrió más de una conexión de base de datos.
Esto sucedió mucho en la aplicación con la que trabajo porque hay muchos módulos en la aplicación, y un hilo puede ejecutarse a través de muchos métodos. Estos métodos abren sus propias conexiones. Como teníamos diferentes desarrolladores que escriben su código, es posible que no vean cómo se inicia el llamado a su código y, por lo tanto, no pudieron ver las transacciones globales de la base de datos que abrió la aplicación.
Según Wikipedia, "un punto muerto es una situación en la que dos o más acciones competidoras están esperando que el otro termine, y nunca lo hace".
... "En informática, el punto muerto de Coffman se refiere a una condición específica cuando dos o más procesos están esperando el uno al otro para liberar un recurso, o más de dos procesos están esperando recursos en una cadena circular".
Creo que dos o más son palabras clave aquí si te mantienes estricto con la definición.
Si amplía la definición del término interbloqueo: un solo subproceso puede encontrarse bloqueado en un bloqueo no reentrante que tomó antes.
Si bien no he usado Java, he bloqueado una aplicación de un solo hilo antes. IIRC: Rutina A ha bloqueado un dato para actualizarlo. La rutina B también bloqueó la misma información para actualizarla. Debido a los cambios en los requisitos, A terminó de llamar a B. Oops.
Por supuesto, este fue solo un error de desarrollo común que capté la primera vez que intenté ejecutar el código, pero se bloqueó por sí mismo. Creo que los bloqueos de este tipo serían posibles en cualquier idioma que admita un sistema de archivos.
Usted escribe un hilo que puede recibir mensajes de otros hilos diciéndole que, por ejemplo, termine. Usted escribe código en el hilo para monitorear otros hilos y enviarlos a terminar mensajes y esperar una respuesta. El hilo se encontraría en la lista, se enviaría un mensaje para terminar y esperaría a que termine. Si no fue escrito de una manera que priorizara los mensajes entrantes durante el estado de espera ...