Conceptos básicos sincronizados

Hemos analizado el uso y el principio de sincronización de superficial a profunda a través de 3 artículos anteriores:

También hay un artículo sobre el diseño de bloqueos de uso común en el control de concurrencia , que comprende los bloqueos en la programación concurrente . Se puede decir que desde el diseño hasta el uso y el principio de realización, se ha realizado un análisis exhaustivo de sincronizado. Hoy usaremos lo que hemos aprendido antes para responder algunos temas candentes.

Consejos : El título es "plagio" de las líneas de "Fantasy Lisa Hair Salon" en el "Concurso Anual de Comedia" . Realizada por PeopleSoft, Mao Tao, Jiang Long, Jiang Shimeng y Ou Jianyu, muy recomendada.

Conceptos básicos sincronizados

Las preguntas básicas se centran principalmente en el uso de sincronizado. Por ejemplo:

  1. ¿Qué representa el objeto lock.class sincronizado?
  2. ¿En qué circunstancias se sincroniza un bloqueo de objeto? ¿En qué circunstancias se produce un bloqueo de clase?
  3. Si se agrega sincronizado a varios métodos del objeto, ¿cuántos bloqueos tiene el objeto?

Al responder este tipo de preguntas, a muchos amigos les gusta memorizar cosas como "sincronizado modifica los métodos estáticos, el alcance de la acción es todo el método estático y los objetos de acción son todos objetos de esta clase", lo que equivale a memorizar directamente las conclusiones e ignorar las principio.

Repasemos primero qué preguntas sincronizadas . El principio mencionado en: Cada objeto en Java está asociado con un monitor. sincronizado bloquea el monitor asociado con el objeto (lo que puede entenderse como bloquear el objeto en sí) y la ejecución solo puede continuar después de que el bloqueo sea exitoso .

Por ejemplo:

public class Human {
	public static synchronized void run() {
		// 业务逻辑
	}
}

Sincronizado modifica los métodos estáticos, y los métodos estáticos son propiedad de la clase. Se puede entender que sincronizado bloquea el objeto Human.class . A continuación, deducimos el fenómeno.

Suponiendo que el subproceso t1 ejecuta el método de ejecución y aún no ha finalizado, es decir, t1 ha bloqueado Human.class y no lo ha liberado, todos los subprocesos que intenten bloquear Human.class se bloquearán en este momento.

Por ejemplo, se bloqueará la ejecución del método de ejecución por el subproceso t2:

Thread t2 = new Thread(Human::run);
t2.start();

¿Qué pasa si agregamos el siguiente método?

public synchronized void eat() {
	// 业务逻辑  
}

El método de instancia modificado sincronizado pertenece al objeto, se puede entender que sincronizado bloquea el objeto actual . Ejecute el siguiente código de prueba, ¿se producirá el bloqueo?

new Thread(Human::run, "t1")).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
	Human human = new Human();
	human.eat();  
}, "t2")).start();

La respuesta es no, porque t1 bloquea el objeto Human.class y t2 bloquea el objeto de instancia Human y no hay competencia entre ellos.

Agregue otro método y ejecute la siguiente prueba, ¿se producirá el bloqueo?

public static synchronized void walk() {
	// 业务逻辑
}  

public static void main(String[] args) throws InterruptedException {
	new Thread(Human::run, "t1").start();
	TimeUnit.SECONDS.sleep(1);
	new Thread(Human::walk, "t2").start();  
}

La respuesta es que el subproceso t2 se bloqueará porque el subproceso t1 y el subproceso t2 compiten por el mismo objeto Human.class y, obviamente, el subproceso t1 bloqueará primero el objeto Human.class.

Finalmente, haga otra prueba y agregue el siguiente método y código de prueba:

public synchronized void drink() {
	// 业务逻辑
}

public static void main(String[] args) throws InterruptedException {
	Human human = new Human();
	new Thread(human::eat, "t1").start();
	TimeUnit.SECONDS.sleep(1);
	new Thread(human::drink, "t2").start();
	new Thread(()-> {
		Human t3 = new Human();
		t3.eat();
	}, "t3").start();

	TimeUnit.SECONDS.sleep(1);
	new Thread(()-> {
		Human t4 = new Human();
		t4.eat();
	}, "t4").start();
}

Los amigos pueden deducir el resultado de ejecución de este código de acuerdo con la combinación de uso y principio.

Consejos : La lógica empresarial puede ejecutar TimeUnit.SECONDS.sleep (60) para simular una retención a largo plazo.

Artículos avanzados sincronizados

El capítulo avanzado examina principalmente el principio de sincronización, por ejemplo:

  • ¿Cómo garantiza sincronizado la atomicidad, el orden y la visibilidad?
  • Describa en detalle el principio de sincronización y el proceso de actualización de bloqueo.
  • ¿Qué significa que sincronizado es un bloqueo pesimista/bloqueo injusto/bloqueo reentrante?

Garantía de simultaneidad sincronizada

Supongamos que tiene el siguiente código:

private static int count = 0;
public static synchronized void add() {
	......
	count++;
	......
}

Bajo la premisa de una sincronización correcta, uno y solo un subproceso puede ejecutar el método agregar al mismo tiempo para modificar el recuento.

En este momento, se "crea" un entorno de subproceso único y el compilador ofrece una garantía "como si fuera en serie" para el reordenamiento, por lo que no habrá problemas de pedido. De manera similar, si solo un hilo ejecuta count++, entonces no hay problema de atomicidad .

En cuanto a la visibilidad, vimos la barrera de la memoria de carga almacenada en la parte de liberar bloqueos pesados ​​en lo que es un bloqueo pesado sincronizado , que garantiza que los datos de la operación de escritura sean visibles para la siguiente operación de lectura.

Consejos :

  • Sincronizado no prohíbe el reordenamiento, pero "crea" un entorno de un solo subproceso;
  • Nos centramos en explicar las barreras de la memoria en volátiles.

El principio de realización de sincronizado.

Sincronizado es un bloqueo de exclusión mutua implementado por la JVM de acuerdo con la idea de diseño del monitor . Cuando sincronizado modifica un bloque de código, las instrucciones monitorenter y monitorexit se agregarán después de la compilación, y cuando se modifica un método, se agregará un indicador de acceso ACC_SYNCHRONIZED.

Después de Java 1.6, la estructura interna de sincronizado en realidad se divide en tres partes: bloqueo sesgado, bloqueo ligero y bloqueo pesado.

Cuando el hilo ingresa al método sincronizado y no hay competencia, modificará la ID del hilo sesgado en el encabezado del objeto. En este momento, sincronizado está en el estado de bloqueo sesgado.

Cuando ocurre una ligera competencia (comúnmente vista en la ejecución alternativa de subprocesos), se actualizará (ampliará) al estado de un candado liviano.

Cuando ocurre una competencia feroz, el candado liviano se actualizará (ampliará) al candado pesado. En este momento, solo un subproceso puede obtener el monitor del objeto, y el resto de los subprocesos se estacionarán (suspenderán) y entrarán en la espera. cola, esperando a que los despierten.

Implementación de funciones sincronizadas

¿Por qué sincronizar es un bloqueo pesimista? Repasemos el bloqueo pesimista mencionado en el próximo artículo Comprensión de los bloqueos en la programación concurrente.Los bloqueos pesimistas creen que el acceso compartido concurrente siempre se modificará, por lo que la operación de bloqueo debe realizarse antes de ingresar a la sección crítica .

Entonces, para sincronizado, ya sea un bloqueo sesgado, un bloqueo liviano o un bloqueo pesado, el bloqueo siempre ocurrirá cuando se usa sincronizado, por lo que es un bloqueo pesimista.

¿Por qué se dice que sincronizado es un bloqueo injusto? Luego revise el bloqueo injusto. La injusticia se refleja en el hecho de que el despertar después del bloqueo no es en el orden de primero en llegar, primero en llegar .

En sincronizado, la estrategia predeterminada es mover los datos en la cola cxq a EntryList y luego activarlos, y no se ejecuta en secuencia. De hecho, no sabemos cuál de los subprocesos en cxq y EntryList entrará primero en espera.

¿Por qué se sincroniza una cerradura reentrante? Mirando hacia atrás en los bloqueos reentrantes, reentrante se refiere a permitir que el mismo hilo se bloquee repetidamente varias veces .

En uso, sincronizado permite que el mismo hilo ingrese varias veces. En la implementación subyacente, sincronizado mantiene internamente el contador _recursiones. Cuando ocurre el reingreso, el contador es +1, y al salir, el contador es -1.

Al nombrar _recursions, también podemos saber que el bloqueo reentrante en Java es el bloqueo recursivo en POSIX.

epílogo

El contenido de este artículo es relativamente simple, principalmente para responder algunas preguntas candentes basadas en el contenido anterior. Sin mencionar aplicar lo aprendido, al menos para poder responder algunas preguntas de la entrevista después de aprender.

Por supuesto, el significado más profundo radica en guiarnos a usar sincronizado de manera razonable y las ideas de diseño que podemos aprender de él.


Si este artículo le resulta útil, felicítelo y apóyelo mucho. Si hay algún error en el artículo, critíquelo y corríjalo. Finalmente, todos pueden prestar atención a Wang Youzhi, un financiero próxima!

 

Supongo que te gusta

Origin blog.csdn.net/m0_74433188/article/details/132553815
Recomendado
Clasificación