sincronización y bloqueo de monitor

1. sincronización y bloqueo de monitor

1.1 Introducción a sincronizado

En Java, sincronizado es una palabra clave utilizada para implementar la sincronización de subprocesos y el control de exclusión mutua. Puede decorar métodos o bloques de código para proteger el acceso a recursos compartidos y evitar problemas de concurrencia causados ​​por múltiples subprocesos que modifican datos al mismo tiempo.

Específicamente, la palabra clave sincronizada funciona de la siguiente manera:

  1. Exclusión mutua: la palabra clave sincronizada garantiza que solo un subproceso pueda ejecutar el método o el bloque de código modificado por sincronizado al mismo tiempo. Cuando un subproceso ingresa a un bloque de código sincronizado, intenta adquirir el bloqueo del monitor del objeto (bloqueo interno). Si el bloqueo ya está ocupado por otros subprocesos, el subproceso actual se bloqueará hasta que se adquiera el bloqueo para ejecutarse, lo que garantiza el acceso exclusivo mutuo de varios subprocesos a los recursos compartidos.

  2. Visibilidad: la palabra clave sincronizada no solo proporciona exclusión mutua, sino que también garantiza que las modificaciones realizadas por un subproceso en una variable compartida antes de liberar el bloqueo sean visibles para otros subprocesos. Esto significa que cuando un subproceso modifica una variable compartida protegida por sincronizada, otros subprocesos pueden ver el último valor después de adquirir el bloqueo, evitando el problema de la inconsistencia de datos.

El uso de la palabra clave sincronizada puede prevenir eficazmente problemas de simultaneidad, como la competencia de datos y las condiciones de carrera causadas por varios subprocesos que acceden a recursos compartidos al mismo tiempo. Es uno de los mecanismos de sincronización más utilizados en Java.

Al usar sincronizado, debe prestar atención a los siguientes puntos:

  1. Los bloques de código que pueden proteger los recursos compartidos deben ser lo más estrechos posible para evitar una competencia de bloqueo innecesaria.

  2. La palabra clave sincronizada se puede utilizar en métodos de instancia, métodos estáticos y bloques de código. Para métodos de instancia y bloques de código, el objeto de bloqueo es la instancia actual; para métodos estáticos y bloques de código, el objeto de bloqueo es el objeto Class de la clase actual.

  3. Si varios subprocesos acceden a diferentes instancias de objetos, los bloqueos entre ellos no se excluyen mutuamente. En otras palabras, sincronizado aísla el acceso simultáneo entre diferentes instancias de objetos.

  4. En algunos casos, el uso de la interfaz de bloqueo y sus clases de implementación (como ReentrantLock) puede proporcionar un control de bloqueo más flexible y detallado, pero el bloqueo debe liberarse manualmente.

En resumen, la palabra clave sincronizada es una herramienta importante en Java para la sincronización de subprocesos y el control de exclusión mutua. Al garantizar la exclusividad y la visibilidad de las secciones críticas, protege eficazmente los recursos compartidos y mejora la seguridad y corrección de los programas de subprocesos múltiples. .

1.2 Bloqueo del monitor (el bloqueo del monitor también se denomina bloqueo interno o mutex)

Cuando se utilizan palabras clave synchronized, se involucra el concepto de bloqueos. Para synchronizedbloques de código o métodos, los bloqueos son un mecanismo que se utiliza para proteger los recursos compartidos.

En Java, cada objeto tiene un bloqueo de monitor (también conocido como bloqueo intrínseco o mutex) asociado con él. Cuando un subproceso desea ingresar un synchronizedbloque de código o método modificado, primero debe adquirir el bloqueo del objeto antes de ejecutar el bloque de código o método.

Object object2 = new Object();

synchronized (object2) {
    // 同步代码块
    // 这里的锁就是对象object2
    // 只有获得object2的锁才能执行这段同步代码块
}

En el código anterior, un object2objeto llamado se usa como objeto de bloqueo. Solo cuando se adquiere el bloqueo object2, es decir, el bloqueo del monitor del objeto se adquiere con éxito , object2se puede ejecutar synchronizedel contenido del bloque de código . Otros object2subprocesos que intentan adquirir el bloqueo se bloquean hasta que se libera el bloqueo.

Por lo tanto, el código en este fragmento de código synchronized (object2)significa que solo un subproceso puede ingresar al bloque de código object2asociado al mismo tiempo synchronized, y otros subprocesos deben esperar a que se libere el bloqueo antes de continuar con la ejecución.

Tenga en cuenta que el objeto de bloqueo puede ser cualquier objeto, "objeto2" aquí es solo un nombre de variable de ejemplo. Es importante compartir el mismo objeto de bloqueo entre varios subprocesos para lograr la sincronización de subprocesos y el control de exclusión mutua.

2. Seguridad del hilo


2.1 Demostración de seguridad de subprocesos


Si varios subprocesos acceden conjuntamente a variables miembro (compartidas) y las modifican, habrá problemas de seguridad de subprocesos.

De la siguiente manera: hay 20 frijoles en la mesa, cada hilo toma 1 frijol en la mesa y la cantidad de frijoles restantes se muestra en la mesa.

Cuando el número de beans que quedan en la mesa es 0, se lanza una excepción.

package day06.threadDemo;
 
public class Demo6 {
    public static void main(String[] args) {
        Table table = new Table();
        Table.Person p1 = table.new Person();
        Table.Person p2 = table.new Person();
        p1.start();
        p2.start();
 
    }
 
}
 
class Table{
    int beans = 20;
    public int getBeans(){
        if(beans ==0){
            throw new RuntimeException("豆没了");
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return beans--;
    }
 
    class Person extends Thread{
        @Override
        public void run() {
            while (true){
                int beans = getBeans();
                System.out.println(this.getName()+":"+beans);
            }
        }
    }
}

Resultados de la:

 Cuando los frijoles restantes en la mesa no sean 0, ve a la mesa para obtener los frijoles.

Cuando quede 1 frijol en la mesa, el hilo 0 y el hilo 1 irán a la mesa para obtener el frijol al mismo tiempo (aquí hay un problema de probabilidad, hemos abierto dos hilos, no necesariamente obtendrán el último frijol al mismo tiempo. Si no al mismo tiempo No habrá problemas de seguridad de subprocesos. Si dos subprocesos toman el último al mismo tiempo), el subproceso 0 toma el último bean, y el subproceso 1 también toma uno, y luego la tabla será Hay -1. 

2.2 Resolviendo problemas de seguridad de subprocesos


1. No acceda a las variables comunes si no puede acceder a ellas, y no las modifique si puede.

2. Bloqueo (pero afectará el rendimiento)

público sincronizado int getBeans()

Bloquee el método getBeans.

Después de que un subproceso ingresa a este método, otros subprocesos no pueden ingresar. Después de que el subproceso ejecuta el programa, se libera el bloqueo y otros subprocesos pueden usar este programa (por ejemplo, varias personas van al baño y entran al baño). gente, otros harán cola)

2.3 Método de bloqueo


Agregue la palabra clave sincronizada al método

    public synchronized int  getBeans(){
        if(beans ==0){
            throw new RuntimeException("豆没了");
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return beans--;
    }


Equivalente a

    public int getBean(){
        synchronized (this){
            if(beans ==0){
                throw new RuntimeException("豆没了");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return beans--;
        }
        }

 En sincronizado (), para ejecutar el recurso (específicamente el bloqueo de qué objeto), este es su propio objeto.
Bloquee todo el método.

Código general:

Aquí está el recurso que usa su propio objeto Tabla como bloqueo.

package com.example.analyzestack.controller;



public class SynchronizedTest {
    public static void main(String[] args) {
        Table table = new Table();
        Table.Person p1= table.new Person();
        Table.Person p2 = table.new Person();
        p1.start();
        p2.start();
    }

}


class Table{
    int beans =20;
    public synchronized int getBeans() {
        // synchronized  加锁
        if(beans==0){
            throw new RuntimeException("豆子没有了");
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return beans--;
    }

    class Person extends Thread{
        @Override
        public void run() {
            while (true){
                int beans =getBeans();
                System.out.println(this.getName()+":"+beans);
            }
        }
    }
}

2.4 Bloqueo de método estático


Método estático, el valor predeterminado es usar la clase de la clase como bloqueo

    // 静态方法,默认使用的是类的class当锁(Table.class)
    public static synchronized void test1(){
        synchronized (Table.class){
            
        }
    }


2.5 Bloqueo no válido


Cuando bloqueamos, bloqueamos el mismo objeto. como esta situación

    public synchronized int  getBeans(){
        Object obj = new Object();
        synchronized (obj){
            if(beans ==0){
                throw new RuntimeException("豆没了");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return beans--;
 
        }


Lock obj, obj no es un objeto de nuestra clase, y se creará un obj cada vez que se llame al método getBeans. Este candado es inútil.

2.6 Interbloqueo


Dos subprocesos, el subproceso 1 ocupa el bloqueo A y va al bloqueo B. El subproceso 2 ocupa el bloqueo B y va al bloqueo A. Los demás no pueden obtener recursos, lo que resulta en un punto muerto.

Por ejemplo

package day06.threadDemo;
 
public class Demo8 {
    public static void main(String[] args) {
        Boo boo = new Boo();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                boo.test1();
            }
        };
 
        Thread t2 = new Thread(){
            @Override
            public void run() {
                boo.test2();
            }
        };
        t1.start();
        t2.start();
 
    }
}
 
class Boo{
    private Object object1 = new Object();
    private Object object2 = new Object();
 
    public void test1(){
        synchronized (object1){
            System.out.println(Thread.currentThread().getName()+"-obj1");
            synchronized (object2){
                System.out.println(Thread.currentThread().getName()+"-obj2");
            }
        }
    }
 
    public void test2(){
        synchronized (object2){
            System.out.println(Thread.currentThread().getName()+"-obj2");
            synchronized (object1){
                System.out.println(Thread.currentThread().getName()+"-obj1");
            }
        }
    }
}

Al ejecutar este programa, el programa se ha ejecutado para siempre. En este punto, está estancado.

Supongo que te gusta

Origin blog.csdn.net/qq_39208536/article/details/131472319
Recomendado
Clasificación