sincronizado en Java: un breve análisis de características, uso, mecanismo de bloqueo y estrategia

Características de sincronizado

Exclusividad mutua

sincronizado garantiza que solo un subproceso pueda ingresar al bloque sincronizado o al método sincronizado al mismo tiempo, evitando el problema de conflicto de que varios subprocesos accedan simultáneamente a recursos compartidos.
sincronizado tendrá un efecto de exclusión mutua. Cuando un subproceso se ejecuta sincronizado en un objeto, otros subprocesos se bloquearán y esperarán si también se ejecutan sincronizados en el mismo objeto.
Veamos un ejemplo a continuación: dos subprocesos adquieren el mismo candado, una vez que se ocupa el candado, el subproceso restante se bloqueará y esperará.

public class test2 {
    
    
    public static void main(String[] args) {
    
    
        Object object = new Object();
        Thread t1 =  new Thread(()->{
    
    
        //进入 synchronized 修饰的代码块, 相当于 加锁
          synchronized (object) {
    
    
              for (int i = 0; i < 5; i++) {
    
    
                  System.out.println("线程t1获取锁");
                  try {
    
    
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
    
    
                      throw new RuntimeException(e);
                  }
              }
          }
        //退出 synchronized 修饰的代码块, 相当于 解锁  
        });
        Thread t2 = new Thread(()->{
    
    
            synchronized (object) {
    
    
                for (int i = 0; i < 5; i++) {
    
    
                    System.out.println("线程特t2获取锁");
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t1.start();
        t2.start();
    }
}

Insertar descripción de la imagen aquí
A partir de los resultados, podemos saber que después de que el subproceso uno libera el bloqueo, el sistema operativo debe despertar el subproceso dos para obtener el bloqueo.

La capa inferior de sincronizado se implementa mediante el bloqueo mutex del sistema operativo.

visibilidad

La visibilidad de la memoria significa que cuando un hilo modifica el valor de una variable compartida, otros hilos pueden ver inmediatamente el valor modificado. En un entorno de subprocesos múltiples, dado que varios subprocesos acceden a variables compartidas al mismo tiempo, cada subproceso tiene su propia memoria de trabajo y la memoria de trabajo almacena una copia parcial de los datos en la memoria principal. Por lo tanto, cuando un subproceso modifica el valor de una variable compartida, pero la modificación aún no se ha descargado a la memoria principal, es posible que otros subprocesos no puedan ver la modificación inmediatamente y continúen usando el valor anterior en su propia memoria de trabajo. provocando invisibilidad de la memoria. .

sincronizado puede garantizar tanto la atomicidad como la visibilidad de la memoria. Las modificaciones de las variables compartidas por un subproceso son visibles para otros subprocesos.

class Counter {
    
    
    public static int flag = 0;
}

public class test3 {
    
    
    public static void main(String[] args) {
    
    
        Object object = new Object();
        Thread t1 = new Thread(() -> {
    
    
            while (true) {
    
    
                synchronized (object) {
    
    
                    if (Counter.flag != 0) {
    
    
                        break;
                    }
                }
            }
            System.out.println("线程一知道了共享变量改为" + Counter.flag);
        });
        Thread t2 = new Thread(() -> {
    
    
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入一个整数:");
            Counter.flag = scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

Insertar descripción de la imagen aquí
Si el hilo no está sincronizado, no se dará cuenta de los cambios en las variables compartidas, por lo que el programa siempre estará ejecutándose.
Insertar descripción de la imagen aquí

Reentrada

El bloque de sincronización sincronizado es reentrante para el mismo subproceso y no habrá problema de bloquearse.
Se puede entender que un hilo no liberó el bloqueo y luego intentó bloquearlo nuevamente.
Según la comprensión anterior de los bloqueos, si el bloqueo no se libera, el bloqueo se bloqueará nuevamente. El segundo bloqueo no se puede obtener hasta que se libere el primer bloqueo, pero el hilo también libera el primer bloqueo. Como resultado, esto El hilo no puede hacer nada ahora y solo puede formar un punto muerto.
Un bloqueo de este tipo se denomina bloqueo no reentrante.

Nuestro sincronizado es un bloqueo reentrante.
Hay dos datos dentro de la cerradura reentrante, a saber, "contador de programa" y "portahilos".

  • Si un hilo descubre que el bloqueo ya está ocupado por otra persona cuando se bloquea, pero resulta que está ocupado por él mismo, aún puede continuar adquiriendo el bloqueo y dejar que el contador se incremente.
  • Cuando el contador disminuye a 0 durante el desbloqueo, el bloqueo se libera.

Cómo usar sincronizado

  1. Modifique directamente los métodos ordinarios: el objeto SynchronizedDemo del bloqueo
public synchronized void methond() {
    
    
}
  1. Modificar método estático: bloquear objeto de la clase SynchronizedDemo
public synchronized static void method() {
    
    
}
  1. Decorar bloque de código: especifique explícitamente qué objeto bloquear
  • Bloquear el objeto actual
public void method() {
    
    
synchronized (this) {
    
    
}
}
  • bloquear objeto
public void method() {
    
    
synchronized (SynchronizedDemo.class) {
    
    
}
}

mecanismo de bloqueo sincronizado

  1. Bloqueo de objetos: la palabra clave sincronizada se puede aplicar directamente a métodos de instancia o bloques de código de instancia. Cuando un hilo ingresa a un método de instancia sincronizado o a un bloque de código de instancia, adquiere automáticamente el bloqueo incorporado del objeto. Solo después de que el hilo libera el bloqueo, otros hilos pueden ingresar al bloque sincronizado.

  2. Bloqueo de clase: la palabra clave sincronizada se puede aplicar a métodos estáticos o bloques de código de clase. Cuando un hilo ingresa a un método estático o bloque de código de clase modificado por sincronizado, adquirirá automáticamente el bloqueo incorporado del objeto Clase de la clase. Los bloqueos de clase pertenecen a toda la clase y, para diferentes instancias de la misma clase, comparten el mismo bloqueo de clase.

  3. Bloquear objeto: puede utilizar la palabra clave sincronizada para bloquear el objeto especificado. Al especificar un objeto como bloqueo, se pueden sincronizar varios subprocesos en función de este objeto. Cuando un subproceso ingresa a un bloque de código sincronizado, intentará adquirir el bloqueo incorporado del objeto especificado. Solo después de que el subproceso libere el bloqueo, otros subprocesos podrán obtener el bloqueo y ejecutar el código sincronizado.

Estrategias de bloqueo comunes

Bloqueo optimista y bloqueo pesimista

El bloqueo pesimista bloquea los datos antes de que se utilicen para evitar que otros subprocesos modifiquen los datos.
El bloqueo optimista verifica si los datos han sido modificados por otros subprocesos al actualizar los datos; de lo contrario, la actualización se realiza correctamente; de ​​lo contrario, devuelve un error.
Synchronized inicialmente utiliza una estrategia de bloqueo optimista. Cuando se descubre que la competencia de bloqueo es frecuente, cambiará automáticamente a una estrategia de bloqueo pesimista.

Cerraduras pesadas y cerraduras ligeras

Un candado liviano es un candado optimizado que utiliza el mecanismo de giro de la CPU durante las operaciones CAS. Si el giro tiene éxito, se adquiere el bloqueo; de lo contrario, se pone en suspensión.
Un bloqueo pesado es un bloqueo tradicional que se implementa mediante el MutexLock (bloqueo mutex) del sistema operativo. Cuando varios subprocesos compiten por el mismo bloqueo, otros subprocesos se bloquearán esperando su liberación.

Bloqueo justo y bloqueo injusto

Supongamos que hay tres subprocesos A, B y C que adquieren el mismo bloqueo en secuencia . El subproceso A adquiere el bloqueo con éxito, pero los subprocesos B y C no logran adquirirlo.
Después de esperar a que el hilo A libere el bloqueo, ¿cómo adquieren el bloqueo los hilos B y C? Insertar descripción de la imagen aquí
Política de bloqueo justa: siga "por orden de llegada". B vino antes que C. Cuando A libera el bloqueo, B puede adquirir el bloqueo antes que C.
Estrategia de bloqueo injusta: no se respeta el principio de "primero en llegar, primero en ser atendido". Tanto B como C pueden adquirir el candado.
sincronizado es un bloqueo injusto

Cerraduras reentrantes y cerraduras no reentrantes

Los bloqueos reentrantes significan que el mismo hilo puede adquirir el mismo bloqueo varias veces.
En Java, cualquier bloqueo cuyo nombre comience con Reentrant es un bloqueo reentrante, y todas las clases de implementación de Lock listas para usar proporcionadas por el JDK, incluidos los bloqueos de palabras clave sincronizadas, son reentrantes.
Se puede entender que un hilo no liberó el bloqueo y luego intentó bloquearlo nuevamente.
Según la comprensión anterior de los bloqueos, si el bloqueo no se libera, el bloqueo se bloqueará nuevamente. El segundo bloqueo no se puede obtener hasta que se libere el primer bloqueo, pero el hilo también libera el primer bloqueo. Como resultado, Este hilo no puede hacer nada ahora y solo puede generar un punto muerto.
Un bloqueo de este tipo se denomina bloqueo no reentrante.

sincronizado es un bloqueo reentrante

bloqueo de giro

Para evitar que el hilo entre en estado bloqueado después de no poder agarrar el candado, llevará mucho tiempo programarlo nuevamente.

while (!locked.compareAndSet(false, true)) {
    
    
            // 不断循环直到获取到锁
        }

Si la adquisición del candado falla, intente adquirirlo nuevamente inmediatamente, realizando un bucle infinito hasta que se adquiera el candado. El primer intento de adquirir el candado falla y el segundo intento se producirá en un período de tiempo muy corto.
Desventajas: si otros subprocesos mantienen el bloqueo durante mucho tiempo, los recursos de la CPU seguirán consumiéndose.
La estrategia de bloqueo ligero en sincronizado probablemente se implemente mediante bloqueos giratorios.

bloqueo de lectura-escritura

Hay dos operaciones principales para que un hilo acceda a los datos: leer datos y escribir datos.

  • Ambos subprocesos solo leen un dato y no hay ningún problema de seguridad de subprocesos en este momento, simplemente léalo al mismo tiempo.

  • Ambos subprocesos tienen que escribir un dato, lo que tiene problemas de seguridad del subproceso.

  • Cuando un hilo lee y otro escribe, también existen problemas de seguridad de los hilos.

    Los bloqueos de lectura y escritura tratan las operaciones de lectura y de escritura de manera diferente. La biblioteca estándar de Java proporciona la clase ReentrantReadWriteLock para implementar bloqueos de lectura y escritura.

  • La clase ReentrantReadWriteLock.ReadLock representa un bloqueo de lectura. Este objeto proporciona métodos de bloqueo/desbloqueo para bloquear y desbloquear.

  • La clase ReentrantReadWriteLock.WriteLock representa un bloqueo de escritura. Este objeto también proporciona métodos de bloqueo/desbloqueo para bloquear y desbloquear.

No existe exclusión mutua entre bloqueo de lectura y bloqueo de lectura. Existe exclusión mutua
entre bloqueo de escritura y bloqueo de escritura. Existe
exclusión mutua entre bloqueo de lectura y bloqueo de escritura.

Sincronizado no es un bloqueo de lectura y escritura

Si quieres saber más, también puedes leer mi columna de notas jajaInsertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/st200112266/article/details/133100680
Recomendado
Clasificación