8 usos de sincronizado, ¿cuántos conoces?

Introducción

Este artículo presentará los escenarios de acceso de 8 métodos de sincronización. Echemos un vistazo a si el método de sincronización de acceso multiproceso sigue siendo seguro para subprocesos en estos 8 casos. Estos escenarios se encuentran a menudo en la programación de subprocesos múltiples y también se hacen preguntas frecuentes durante las entrevistas, por lo que ya sea teoría o práctica, estos son los escenarios que deben dominarse en escenarios de subprocesos múltiples.

Ocho escenarios de uso:

A continuación, implementémoslo mediante código para juzgar si los siguientes escenarios son seguros para subprocesos y por qué.

  1. Método de sincronización para que dos subprocesos accedan al mismo objeto al mismo tiempo
  2. Método de sincronización para que dos subprocesos accedan a dos objetos al mismo tiempo
  3. Método estático sincronizado para que dos subprocesos accedan (uno o dos) objetos al mismo tiempo
  4. Métodos sincronizados y no sincronizados de dos subprocesos que acceden a (uno o dos) objetos al mismo tiempo, respectivamente
  5. Dos subprocesos acceden a un método sincronizado en el mismo objeto y el método sincronizado llama a un método no sincronizado.
  6. Diferentes métodos de sincronización para que dos subprocesos accedan al mismo objeto al mismo tiempo
  7. Dos subprocesos acceden a métodos sincronizados estáticos y sincronizados no estáticos al mismo tiempo, respectivamente
  8. Después de que el método de sincronización genera una excepción, la JVM liberará automáticamente el bloqueo

Escenario 1: método de sincronización para que dos subprocesos accedan al mismo objeto al mismo tiempo

Análisis: esta situación es un bloqueo de método en un bloqueo de objeto clásico. Dos subprocesos compiten por el mismo bloqueo de objeto, por lo que se esperarán entre sí, lo cual es seguro para subprocesos.

"Un método sincronizado en el que dos subprocesos acceden al mismo objeto al mismo tiempo es seguro para subprocesos".

Escenario 2: método de sincronización para que dos subprocesos accedan a dos objetos al mismo tiempo

Este tipo de escena es la escena donde falla el bloqueo del objeto, la razón es que se accede al método de sincronización de dos objetos, luego los dos subprocesos mantienen los bloqueos de los dos subprocesos respectivamente, por lo que no estarán restringidos entre sí. El propósito del bloqueo es permitir que varios subprocesos compitan por el mismo candado. En este caso, varios subprocesos ya no compiten por el mismo candado, sino que mantienen un candado por separado, por lo que nuestra conclusión es:

"Un método sincronizado en el que dos subprocesos acceden a dos objetos al mismo tiempo no es seguro para subprocesos".

Verificación de código:

public class Condition2 implements Runnable {  
    // 创建两个不同的对象  
 static Condition2 instance1 = new Condition2();  
 static Condition2 instance2 = new Condition2();  
  
 @Override  
 public void run() {  
  method();  
 }  
  
 private synchronized void method() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",运行结束");  
 }  
  
 public static void main(String[] args) {  
  Thread thread1 = new Thread(instance1);  
  Thread thread2 = new Thread(instance2);  
  thread1.start();  
  thread2.start();  
  while (thread1.isAlive() || thread2.isAlive()) {  
  }  
  System.out.println("测试结束");  
 }  
}  

resultado de la operación:

Se ejecutan dos subprocesos en paralelo, por lo que no es seguro para subprocesos.

线程名:Thread-0,运行开始  
线程名:Thread-1,运行开始  
线程:Thread-0,运行结束  
线程:Thread-1,运行结束  
测试结束  

Análisis de código:

"El problema es este:"

Dos subprocesos (hilo1, subproceso2), acceden al método de sincronización (método ()) de dos objetos (instancia1, instancia2), ambos subprocesos tienen sus propios bloqueos y no pueden formar una situación en la que dos subprocesos compitan por un bloqueo, por lo que esto, el El método modificado sincronizado método () tiene el mismo efecto que el método modificado no sincronizado (si no me cree, elimine la palabra clave sincronizada, el resultado de la operación es el mismo), por lo que el método () en este momento es solo un método común.

"Cómo resolver este problema:"

Para que el bloqueo surta efecto, solo necesita modificar el método método () con estático, formando así un bloqueo de clase. Varias instancias (instancia1, instancia2) compiten juntas por un bloqueo de clase y se pueden ejecutar dos subprocesos en serie. De esto se trata la siguiente escena.

Escenario 3: método de sincronización estática para que dos subprocesos accedan a (uno o dos) objetos al mismo tiempo

Este escenario resuelve el problema de inseguridad de subprocesos que ocurre en el escenario 2, es decir, se implementa con bloqueos de clase:

"Un método sincronizado estáticamente en el que dos subprocesos acceden a (uno o dos) objetos simultáneamente es seguro para subprocesos".

Escenario 4: método de sincronización y método de no sincronización de dos subprocesos que acceden a (uno o dos) objetos al mismo tiempo

En este escenario, uno de los dos subprocesos accede al método síncrono y el otro accede al método no sincronizado, en este momento el programa se ejecutará en serie, es decir, ¿es seguro para subprocesos?
Podemos estar seguros de que el hilo no es seguro. Si el método synchronizedes seguro sin agregarlo, entonces no hay necesidad de un método de sincronización. Verifique nuestra conclusión:

"Un método sincronizado y un método no sincronizado de (uno o dos) objetos a los que acceden dos subprocesos al mismo tiempo, respectivamente, no son seguros para subprocesos".

public class Condition4 implements Runnable {  
  
 static Condition4 instance = new Condition4();  
  
 @Override  
 public void run() {  
  //两个线程访问同步方法和非同步方法  
  if (Thread.currentThread().getName().equals("Thread-0")) {  
   //线程0,执行同步方法method0()  
   method0();  
  }  
  if (Thread.currentThread().getName().equals("Thread-1")) {  
   //线程1,执行非同步方法method1()  
   method1();  
  }  
 }  
      
    // 同步方法  
 private synchronized void method0() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",同步方法,运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",同步方法,运行结束");  
 }  
      
    // 普通方法  
 private void method1() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",普通方法,运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",普通方法,运行结束");  
 }  
  
 public static void main(String[] args) {  
  Thread thread1 = new Thread(instance);  
  Thread thread2 = new Thread(instance);  
  thread1.start();  
  thread2.start();  
  while (thread1.isAlive() || thread2.isAlive()) {  
  }  
  System.out.println("测试结束");  
 }  
  
}  

resultado de la operación:

Se ejecutan dos subprocesos en paralelo, por lo que no es seguro para subprocesos.

线程名:Thread-0,同步方法,运行开始  
线程名:Thread-1,普通方法,运行开始  
线程:Thread-0,同步方法,运行结束  
线程:Thread-1,普通方法,运行结束  
测试结束  

Análisis de resultados

El problema es este: el método 1 no se modifica mediante sincronizado, por lo que no se verá afectado por el bloqueo. Incluso en el mismo objeto, por supuesto, en múltiples instancias, no se verá afectado por el bloqueo. en conclusión:

"Los métodos asincrónicos no se ven afectados por otros métodos sincronizados modificados por sincronizados"

Puede pensar en un escenario similar: varios subprocesos acceden a un método sincronizado en el mismo objeto y el método sincronizado llama a un método no sincronizado. ¿Será este escenario seguro para subprocesos?

Escenario 5: dos subprocesos acceden a un método de sincronización en el mismo objeto y el método de sincronización llama a un método no sincronizado

Experimentemos con este escenario, usemos dos subprocesos para llamar al método de sincronización y llamemos al método normal en el método de sincronización; luego usemos un subproceso para llamar directamente al método normal para ver si es seguro para subprocesos.

public class Condition8 implements Runnable {  
  
 static Condition8 instance = new Condition8();  
  
 @Override  
 public void run() {  
  if (Thread.currentThread().getName().equals("Thread-0")) {  
   //直接调用普通方法  
   method2();  
  } else {  
   // 先调用同步方法,在同步方法内调用普通方法  
   method1();  
  }  
 }  
  
 // 同步方法  
 private static synchronized void method1() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",同步方法,运行开始");  
  try {  
   Thread.sleep(2000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",同步方法,运行结束,开始调用普通方法");  
  method2();  
 }  
  
 // 普通方法  
 private static void method2() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",普通方法,运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",普通方法,运行结束");  
 }  
  
 public static void main(String[] args) {  
  // 此线程直接调用普通方法  
  Thread thread0 = new Thread(instance);  
  // 这两个线程直接调用同步方法  
  Thread thread1 = new Thread(instance);  
  Thread thread2 = new Thread(instance);  
  thread0.start();  
  thread1.start();  
  thread2.start();  
  while (thread0.isAlive() || thread1.isAlive() || thread2.isAlive()) {  
  }  
  System.out.println("测试结束");  
 }  
  
}  

resultado de la operación:

线程名:Thread-0,普通方法,运行开始  
线程名:Thread-1,同步方法,运行开始  
线程:Thread-1,同步方法,运行结束,开始调用普通方法  
线程名:Thread-1,普通方法,运行开始  
线程:Thread-0,普通方法,运行结束  
线程:Thread-1,普通方法,运行结束  
线程名:Thread-2,同步方法,运行开始  
线程:Thread-2,同步方法,运行结束,开始调用普通方法  
线程名:Thread-2,普通方法,运行开始  
线程:Thread-2,普通方法,运行结束  
测试结束  

Análisis de resultados:

Podemos ver que los métodos ordinarios se ejecutan en paralelo mediante dos subprocesos y no son seguros para subprocesos. ¿Por qué es esto?

Porque si cualquier otro subproceso llama directamente al método no sincronizado, en lugar de llamar al método no sincronizado solo cuando se llama al método síncrono, en este momento, varios subprocesos ejecutarán el método no sincronizado en paralelo, y el subproceso no es seguro.

Al llamar a un método no sincronizado en un método síncrono, para garantizar la seguridad de los subprocesos, es necesario asegurarse de que la entrada del método no sincronizado solo aparezca en el método síncrono. Sin embargo, este método de control no es lo suficientemente elegante: si una persona desconocida llama directamente al método asincrónico, la sincronización del hilo original ya no será segura. Por lo tanto, no se recomienda que todos lo utilicen de esta manera en el proyecto, pero debemos comprender esta situación y debemos abordar los problemas de seguridad de los subprocesos de una manera que tenga una semántica clara y permita a las personas saber que esto es un método sincrónico de un vistazo.

Por lo tanto, la forma más sencilla es agregar synchronizedpalabras clave al método no sincronizado para convertirlo en un método sincrónico, que se convierte en "Escenario 5: diferentes métodos de sincronización para que dos subprocesos accedan al mismo objeto al mismo tiempo". En este escenario, todos pueden Vea claramente que los dos métodos de sincronización en el mismo objeto son seguros para subprocesos sin importar qué subproceso los llame.

Entonces la conclusión es:

"Dos subprocesos acceden a un método sincronizado en el mismo objeto, y el método sincronizado llama a un método no sincronizado. Es seguro para subprocesos solo si ningún otro subproceso llama directamente al método no sincronizado. Si otros subprocesos llaman directamente al método no sincronizado método, no es seguro para subprocesos".

Escenario 6: diferentes métodos de sincronización para que dos subprocesos accedan al mismo objeto al mismo tiempo

Esta escena también analiza el alcance del bloqueo del objeto, es decir, todos los métodos de sincronización en el objeto. Entonces, al acceder a múltiples métodos sincronizados en el mismo objeto, la conclusión es:

"Dos subprocesos son seguros cuando acceden a diferentes métodos sincronizados del mismo objeto al mismo tiempo".

public class Condition5 implements Runnable {  
 static Condition5 instance = new Condition5();  
  
 @Override  
 public void run() {  
  if (Thread.currentThread().getName().equals("Thread-0")) {  
   //线程0,执行同步方法method0()  
   method0();  
  }  
  if (Thread.currentThread().getName().equals("Thread-1")) {  
   //线程1,执行同步方法method1()  
   method1();  
  }  
 }  
  
 private synchronized void method0() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",同步方法0,运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",同步方法0,运行结束");  
 }  
  
 private synchronized void method1() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",同步方法1,运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",同步方法1,运行结束");  
 }  
  
 //运行结果:串行  
 public static void main(String[] args) {  
  Thread thread1 = new Thread(instance);  
  Thread thread2 = new Thread(instance);  
  thread1.start();  
  thread2.start();  
  while (thread1.isAlive() || thread2.isAlive()) {  
  }  
  System.out.println("测试结束");  
 }  
}  

resultado de la operación:

es seguro para subprocesos.

线程名:Thread-1,同步方法1,运行开始  
线程:Thread-1,同步方法1,运行结束  
线程名:Thread-0,同步方法0,运行开始  
线程:Thread-0,同步方法0,运行结束  
测试结束  

Análisis de resultados:

El modificador sincronizado de los dos métodos (método0() y método1()) no especifica un objeto de bloqueo, pero el objeto de bloqueo predeterminado es este objeto como objeto de bloqueo, por lo que para la misma instancia (instancia), el bloqueo obtenido
por los dos subprocesos tienen el mismo bloqueo y el método de sincronización se ejecutará en serie en este momento. Esto también es synchronizeduna manifestación de la reentrada de palabras clave.

Escenario 7: dos subprocesos acceden a métodos sincronizados estáticos y sincronizados no estáticos al mismo tiempo

La esencia de este escenario también es discutir si los dos subprocesos adquieren el mismo bloqueo. Los métodos estáticos synchronizedpertenecen a bloqueos de clase, los objetos bloqueados son (*.class)objetos, los métodos no estáticos synchronizedpertenecen a bloqueos de métodos en bloqueos de objetos y los objetos bloqueados son thisobjetos. Los dos hilos obtienen bloqueos diferentes, por lo que, naturalmente, no se afectarán entre sí. en conclusión:

"Dos subprocesos acceden a los métodos sincronizados estáticos y sincronizados no estáticos al mismo tiempo, y el subproceso no es seguro".

Código:

public class Condition6 implements Runnable {  
 static Condition6 instance = new Condition6();  
  
 @Override  
 public void run() {  
  if (Thread.currentThread().getName().equals("Thread-0")) {  
   //线程0,执行静态同步方法method0()  
   method0();  
  }  
  if (Thread.currentThread().getName().equals("Thread-1")) {  
   //线程1,执行非静态同步方法method1()  
   method1();  
  }  
 }  
  
 // 重点:用static synchronized 修饰的方法,属于类锁,锁对象为(*.class)对象。  
 private static synchronized void method0() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",静态同步方法0,运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",静态同步方法0,运行结束");  
 }  
  
 // 重点:synchronized 修饰的方法,属于方法锁,锁对象为(this)对象。  
 private synchronized void method1() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",非静态同步方法1,运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",非静态同步方法1,运行结束");  
 }  
  
 //运行结果:并行  
 public static void main(String[] args) {  
  //问题原因: 线程1的锁是类锁(*.class)对象,线程2的锁是方法锁(this)对象,两个线程的锁不一样,自然不会互相影响,所以会并行执行。  
  Thread thread1 = new Thread(instance);  
  Thread thread2 = new Thread(instance);  
  thread1.start();  
  thread2.start();  
  while (thread1.isAlive() || thread2.isAlive()) {  
  }  
  System.out.println("测试结束");  
 } 

resultado de la operación:

线程名:Thread-0,静态同步方法0,运行开始  
线程名:Thread-1,非静态同步方法1,运行开始  
线程:Thread-1,非静态同步方法1,运行结束  
线程:Thread-0,静态同步方法0,运行结束  
测试结束  

Escenario 8: después de que el método de sincronización genera una excepción, la JVM liberará automáticamente el bloqueo

Este escenario explora synchronizedel escenario de liberar un bloqueo:

"El bloqueo sólo se liberará cuando el método de sincronización se complete o genere una excepción durante la ejecución".

Por lo tanto, cuando ocurre una excepción en el método de sincronización de un subproceso, el bloqueo se liberará y el otro subproceso obtendrá el bloqueo y continuará ejecutándose. En lugar de que un subproceso arroje una excepción, otro subproceso ha estado esperando adquirir el bloqueo. Esto se debe a que la JVM liberará automáticamente el objeto de bloqueo cuando el método de sincronización genere una excepción.

Código:

public class Condition7 implements Runnable {  
  
 private static Condition7 instance = new Condition7();  
  
 @Override  
 public void run() {  
  if (Thread.currentThread().getName().equals("Thread-0")) {  
   //线程0,执行抛异常方法method0()  
   method0();  
  }  
  if (Thread.currentThread().getName().equals("Thread-1")) {  
   //线程1,执行正常方法method1()  
   method1();  
  }  
 }  
  
 private synchronized void method0() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  //同步方法中,当抛出异常时,JVM会自动释放锁,不需要手动释放,其他线程即可获取到该锁  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",抛出异常,释放锁");  
  throw new RuntimeException();  
  
 }  
  
 private synchronized void method1() {  
  System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");  
  try {  
   Thread.sleep(4000);  
  } catch (InterruptedException e) {  
   e.printStackTrace();  
  }  
  System.out.println("线程:" + Thread.currentThread().getName() + ",运行结束");  
 }  
  
 public static void main(String[] args) {  
  Thread thread1 = new Thread(instance);  
  Thread thread2 = new Thread(instance);  
  thread1.start();  
  thread2.start();  
  while (thread1.isAlive() || thread2.isAlive()) {  
  }  
  System.out.println("测试结束");  
 }  
  
}  

resultado de la operación:

线程名:Thread-0,运行开始  
线程名:Thread-0,抛出异常,释放锁  
线程名:Thread-1,运行开始  
Exception in thread "Thread-0" java.lang.RuntimeException  
 at com.study.synchronize.conditions.Condition7.method0(Condition7.java:34)  
 at com.study.synchronize.conditions.Condition7.run(Condition7.java:17)  
 at java.lang.Thread.run(Thread.java:748)  
线程:Thread-1,运行结束  
测试结束  

Análisis de resultados:

Se puede ver que el subproceso todavía se ejecuta en serie, lo que indica que es seguro para subprocesos. Además, después de que ocurre una excepción, no causará un fenómeno de interbloqueo: la JVM liberará automáticamente el objeto de bloqueo del subproceso anormal y otros subprocesos adquirirán el bloqueo y continuarán ejecutándose.

Resumir

Este artículo resume, implementa y verifica synchronizedvarios escenarios de uso con código, así como las razones y conclusiones de varios escenarios. La base teórica de nuestro análisis es ¿ synchronizedquién es el objeto de bloqueo de la palabra clave? ¿Hay varios subprocesos compitiendo por el mismo candado? De acuerdo con esta condición para determinar si el hilo es seguro. Por lo tanto, con el análisis y ejercicio de estos escenarios, cuando usemos programación multiproceso en el futuro, también podemos juzgar si el hilo es seguro analizando el objeto de bloqueo, para evitar tales problemas.

Este artículo cubre synchronizedlos escenarios de uso más importantes de las palabras clave y también es una pregunta frecuente que los entrevistadores hacen. Es un artículo que vale la pena leer detenidamente y practicar. Si te gusta este artículo, dale me gusta y márcalo como favorito. .

Práctica de proyectos Java + archivos adjuntos: sistema de comercio electrónico CRMEB_Java: centro comercial Java, código abierto, versión JAVA del centro comercial CRMEB, SpringBoot + Maven + Swagger + Mybatis Plus + Redis + Uniapp + Vue + elementUI incluye terminal móvil, programa pequeño, fondo de PC, interfaz Api ; Módulos como productos, usuarios, carritos de compras, pedidos, puntos, cupones, marketing, saldos, permisos, roles, configuraciones del sistema, datos combinados, formularios arrastrables, etc., reducen en gran medida el costo de la segunda apertura.

Autor: Pila de tecnología Java

Supongo que te gusta

Origin blog.csdn.net/weixin_64051447/article/details/132610119
Recomendado
Clasificación