Compreensão de bloqueios reentrantes em JAVA

Graças a "StoneWang" por um resumo profundo dos bloqueios reentrantes: Reentrante significa que um thread já adquiriu um determinado bloqueio e pode adquiri-lo novamente sem conflito.

1. Bloqueio reentrante

  • sincronizado
  • ReentrantLock

Duas características de bloqueios reentrantes:

  • Quando um segmento A adquire o bloqueio, o segmento A pode obter o bloqueio novamente sem liberar o bloqueio atual
  • Outros threads não podem obter esse bloqueio. Somente depois que este thread A libera o bloqueio atual, outros threads podem adquirir este bloqueio.

1.1 As características dos bloqueios reentrantes sincronizados:

public class SynchronizedLockTest {
    
    
    //类锁,实际是锁类的class对象
    private static synchronized void synClass(){
    
    

        Thread thread = Thread.currentThread();
        System.out.println("当前执行synClass的线程名称:" + thread.getName());

        System.out.println("synClass going...");
            subSynClass();
        System.out.println("synClass end");
    }



    private static synchronized void subSynClass(){
    
    
        Thread thread = Thread.currentThread();
        System.out.println("当前执行 subSynClass 的线程名称:" + thread.getName());

        System.out.println("subSynClass going...");
        SleepTools.second(3);
        System.out.println("subSynClass end");
    }

    public static void main(String[] args) {
    
    
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("第一个线程开始执行");
                synClass();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("第二个线程开始执行");
                synClass();
            }
        });

        thread.setName("第一个线程");
        thread2.setName("第二个线程");

        thread.start();
        thread2.start();


    }
}

Resultado de saída:

第一个线程开始执行
第二个线程开始执行
当前执行synClass的线程名称:第一个线程
synClass going...
当前执行 subSynClass 的线程名称:第一个线程
subSynClass going...
subSynClass end
synClass end
当前执行synClass的线程名称:第二个线程
synClass going...
当前执行 subSynClass 的线程名称:第二个线程
subSynClass going...
subSynClass end
synClass end

Você pode ver que o bloqueio de classe sincronizado é usado neste exemplo, o que significa que há apenas um bloqueio em toda esta classe.

  • O primeiro recurso de reentrada sincronizada é mostrado em: Quando o "primeiro thread" executa o método SynClass () sincronizado e obtém o bloqueio, você ainda pode chamar a subSynClass sincronizada em SynClass () ()método.
  • O segundo recurso de reentrada sincronizada: quando a "segunda thread" chama synClass. Como o "primeiro encadeamento" não liberou o bloqueio, o "segundo encadeamento" bloqueará e aguardará.

Nota: synchronized irá liberar o bloqueio quando o método ou bloco de código for executado

1.2 Recursos do bloqueio reentrante ReentrantLock:

public class ReentrantLockTest2 {
    
    
    static Lock lock = new ReentrantLock();


    public static void methodTest() {
    
    
        System.out.println(Thread.currentThread().getName() + "进入方法等待.....");
        try {
    
    
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "methodTest do something start。。。。");
             subMethodTest();
            System.out.println(Thread.currentThread().getName() + "methodTest do something end。。。。。");

        } finally {
    
    
            lock.unlock();
        }

    }


    public static void subMethodTest() {
    
    
        try {
    
    
            lock.lock();

            System.out.println(Thread.currentThread().getName() + "subMethodTest do something start");
            SleepTools.second(1);
            System.out.println(Thread.currentThread().getName() + "subMethodTest do something end");

        } finally {
    
    
            lock.unlock();
        }

    }


    public static void main(String[] args) {
    
    
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                methodTest();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                methodTest();
            }
        });

        thread.setName("A线程");
        thread2.setName("B线程");

        thread.start();
        thread2.start();

    }
}

Resultado de saída

A线程进入方法等待.....
B线程进入方法等待.....
A线程methodTest do something start。。。。
A线程subMethodTest do something start
A线程subMethodTest do something end
A线程methodTest do something end。。。。。
B线程methodTest do something start。。。。
B线程subMethodTest do something start
B线程subMethodTest do something end
B线程methodTest do something end。。。。。
  • O primeiro recurso da reentrada de ReentrantLock é mostrado em: Um thread executa methodTest () para obter o bloqueio de bloqueio e, quando subMethodTest () é chamado, ele ainda pode inserir o segmento de código bloqueado pelo bloqueio.
  • O segundo recurso de reentrada sincronizada: quando o thread B chama methodTest (), o bloco de código bloqueado pela fechadura não pode entrar e apenas espera do lado de fora.

A diferença entre ReentrantLock e synchronized é que você precisa liberar manualmente o bloqueio, então você deve liberar manualmente o bloqueio ao usar ReentrantLock, e o número de bloqueios deve ser igual ao número de liberações.

  • Se o "lock.unlock ();" no método methodTest () do código acima estiver comentado, o resultado da impressão é o seguinte:
B线程进入方法等待.....
A线程进入方法等待.....
B线程methodTest do something start。。。。
B线程subMethodTest do something start
B线程subMethodTest do something end
B线程methodTest do something end。。。。。

O thread B libera o bloqueio apenas uma vez, e o thread A será bloqueado para sempre e não terá a chance de ser executado.

1.3 Implementação de bloqueio reentrante

public class Lock{
    
    
    boolean isLocked = false;
    Thread  lockedBy = null;
    int lockedCount = 0;
    public synchronized void lock()
            throws InterruptedException{
    
    
        Thread thread = Thread.currentThread();
        while(isLocked && lockedBy != thread){
    
    
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = thread;
    }
    public synchronized void unlock(){
    
    
        if(Thread.currentThread() == this.lockedBy){
    
    
            lockedCount--;
            if(lockedCount == 0){
    
    
                isLocked = false;
                notify();
            }
        }
    }
}

Lógica ao bloquear:

  1. Ao bloquear, obtenha o segmento atual primeiro. (Identifique quem precisa da fechadura)
 Thread thread = Thread.currentThread();
  1. Julgamento: Quando o recurso crítico foi bloqueado, mas o thread que está solicitando o bloqueio não é o thread que bloqueou anteriormente o recurso crítico. Então, o thread que está solicitando o bloqueio precisa esperar.
while(isLocked && lockedBy != thread){
    
    
        wait();
}

Duas situações em que o bloqueio pode ser obtido sem espera:

  • O bloqueio atual não é usado por threads.
  • O bloqueio atual é usado por um encadeamento e o encadeamento que está solicitando o bloqueio é o que está usando o bloqueio.
  1. Depois de obter o bloqueio
isLocked = true; // 当前锁已经被使用了
lockedCount++;//锁的次数+1
lockedBy = thread;//记录拿到锁的线程

O thread que está solicitando o bloqueio primeiro adiciona o bloqueio e, em seguida, o número de bloqueios + 1 e, em seguida, atribui a si mesmo (este segmento) para lockedBy para mostrar quem atualmente usa o bloqueio para facilitar o julgamento do tempo ao entrar novamente.

Lógica de desbloqueio:

  1. Primeiro, veja se o segmento que requer desbloqueio é o segmento que está usando o bloqueio atualmente. Não fazendo nada. (Claro, você não pode permitir que outros threads executem o código de desbloqueio para desbloquear e usá-lo à vontade. Isso equivale a todos os que têm uma chave. O julgamento aqui significa que o desbloqueio deve ser bloqueado)
  2. Em seguida, reduza o número de bloqueios em um.
  3. Em seguida, é avaliado se o número de bloqueios tornou-se 0.
  4. Mudar para 0 significa que o bloqueio foi totalmente desbloqueado. O sinalizador bloqueado islocked pode ser redefinido.
    E despertar aleatoriamente um thread esperando por wait (): notificar ()

2. Bloqueio não reentrante

Como o mesmo encadeamento pode entrar no mesmo bloqueio várias vezes é chamado de bloqueio reentrante, um bloqueio não reentrante significa que o mesmo encadeamento não pode entrar no mesmo bloqueio várias vezes.

public class NotReentrantLock{
    
    
    private boolean isLocked = false;

    public synchronized void lock() {
    
    
        while(isLocked){
    
    
            try {
    
    
                wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        isLocked = true;
    }
    public synchronized void unlock(){
    
    
        isLocked = false;
        notify();
    }




    public static void main(String[] args) {
    
    
        NotReentrantLock lock = new NotReentrantLock();

        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    lock.lock();
                    System.out.println("第1次获取锁,这个锁是:" + lock);

                    int index = 1;
                    while (true) {
    
    
                        try {
    
    
                            lock.lock();
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);

                            try {
    
    
                                Thread.sleep(new Random().nextInt(200));
                            } catch (InterruptedException e) {
    
    
                                e.printStackTrace();
                            }

                            if (index == 10) {
    
    
                                break;
                            }
                        } finally {
    
    
                            lock.unlock();
                        }

                    }

                } finally {
    
    
                    lock.unlock();
                }
            }
        }).start();
    }
}

Resultado:

1次获取锁,这个锁是:com.xiangxue.可重入锁.NotReentrantLock@15c02541

Você pode ver que o mesmo thread é bloqueado quando o bloqueio é adquirido pela segunda vez

Referência:
https://blog.csdn.net/w8y56f/article/details/89554060
https://blog.csdn.net/qq_29519041/article/details/86583945

Acho que você gosta

Origin blog.csdn.net/langwuzhe/article/details/108556899
Recomendado
Clasificación