Ejemplo de Java: bloqueo mutuo y su solución
Ejemplo de Java
Un bloqueo mutuo es una situación en la que varios hilos están bloqueados al mismo tiempo, y uno o todos ellos están esperando a que se libere algún recurso. Dado que los hilos están bloqueados indefinidamente, el programa no puede terminar normalmente.
Las cuatro condiciones necesarias para que se produzca un bloqueo mutuo en Java son:
- 1. Uso mutuamente excluyente: cuando un recurso es utilizado (ocupado) por un hilo, otros hilos no pueden usarlo.
- 2. No apropiable: el solicitante de un recurso no puede arrebatarlo por la fuerza al propietario; el recurso solo puede ser liberado activamente por quien lo posee.
- 3. Solicitud y retención: el solicitante de recursos mantiene la posesión de los recursos originales mientras solicita otros recursos.
- 4. Espera circular: existe una cola de espera en la que P1 ocupa el recurso de P2, P2 ocupa el recurso de P3 y P3 ocupa el recurso de P1. Esto forma un bucle de espera.
Cuando se cumplen las cuatro condiciones anteriores, se forma un bloqueo mutuo. Por supuesto, si en una situación de bloqueo mutuo se rompe cualquiera de las condiciones anteriores, el bloqueo mutuo desaparece. A continuación, usamos código Java para simular la generación de un bloqueo mutuo.
La forma de resolver el problema del bloqueo mutuo es: una es usar `synchronized` y otra es usar el bloqueo explícito `Lock`.
Sin embargo, si los bloqueos se usan de forma inadecuada y se necesita bloquear varios objetos al mismo tiempo, se producirá un bloqueo mutuo, como se muestra a continuación:
Archivo LockTest.java
import java.util.Date;
public class LockTest {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args) {
LockA la = new LockA();
new Thread(la).start();
LockB lb = new LockB();
new Thread(lb).start();
}
}
class LockA implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + "LockA comienza a ejecutarse");
while(true){
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + "LockA bloquea obj1");
Thread.sleep(3000);
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + "LockA bloquea obj2");
Thread.sleep(60 * 1000);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockB implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + "LockB comienza a ejecutarse");
while(true){
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + "LockB bloquea obj2");
Thread.sleep(3000);
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + "LockB bloquea obj1");
Thread.sleep(60 * 1000);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
El resultado de ejecutar el código anterior es:
Tue May 05 10:51:06 CST 2015 LockB 开始执行
Tue May 05 10:51:06 CST 2015 LockA 开始执行
Tue May 05 10:51:06 CST 2015 LockB 锁住 obj2
Tue May 05 10:51:06 CST 2015 LockA 锁住 obj1
En este momento se produce el bloqueo mutuo.
Para resolver este problema, no usamos bloqueos explícitos; usamos semáforos para controlar.
Un semáforo puede controlar cuántos hilos pueden acceder a un recurso. Aquí especificamos que solo un hilo pueda acceder, logrando así un efecto similar al bloqueo. Además, el semáforo puede especificar un tiempo de espera para adquirirlo, y podemos realizar un procesamiento adicional basado en ese tiempo de espera.
Para los casos en los que no se puede adquirir con éxito, generalmente se reintenta, o se especifica un número de intentos, o también se puede salir de inmediato.
Veamos el siguiente código:
Archivo UnLockTest.java
import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class UnLockTest {
public static String obj1 = "obj1";
public static final Semaphore a1 = new Semaphore(1);
public static String obj2 = "obj2";
public static final Semaphore a2 = new Semaphore(1);
public static void main(String[] args) {
LockAa la = new LockAa();
new Thread(la).start();
LockBb lb = new LockBb();
new Thread(lb).start();
}
}
class LockAa implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + "LockA comienza a ejecutarse");
while (true) {
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + "LockA bloquea obj1");
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + "LockA bloquea obj2");
Thread.sleep(60 * 1000);
}else{
System.out.println(new Date().toString() + "LockA falla al bloquear obj2");
}
}else{
System.out.println(new Date().toString() + "LockA falla al bloquear obj1");
}
UnLockTest.a1.release();
UnLockTest.a2.release();
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockBb implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + "LockB comienza a ejecutarse");
while (true) {
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + "LockB bloquea obj2");
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + "LockB bloquea obj1");
Thread.sleep(60 * 1000);
}else{
System.out.println(new Date().toString() + "LockB falla al bloquear obj1");
}
}else{
System.out.println(new Date().toString() + "LockB falla al bloquear obj2");
}
UnLockTest.a1.release();
UnLockTest.a2.release();
Thread.sleep(10 * 1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
El resultado de salida del código de ejemplo anterior es:
Tue May 05 10:59:13 CST 2015 LockA 开始执行
Tue May 05 10:59:13 CST 2015 LockB 开始执行
Tue May 05 10:59:13 CST 2015 LockB 锁住 obj2
Tue May 05 10:59:13 CST 2015 LockA 锁住 obj1
Tue May 05 10:59:14 CST 2015LockB 锁 obj1 失败
Tue May 05 10:59:14 CST 2015LockA 锁 obj2 失败
Tue May 05 10:59:15 CST 2015 LockA 锁住 obj1
Tue May 05 10:59:15 CST 2015 LockA 锁住 obj2
Ejemplo de Java
Otras extensiones