El principio subyacente de ReentrantLock

Tabla de contenido

1. Primeros pasos con ReentrantLock

2. Principio de AQS

1. Introducción a AQS

2. Cerradura personalizada

3. Principio de implementación de ReentrantLock

1. Implementación de bloqueo injusto

Proceso de bloqueo

Liberar proceso de bloqueo

2. Principio de reentrada

3. Principio interrumpible

4. Principio de bloqueo justo

5. Principio de la variable de condición

esperar proceso

proceso de señal


1. Primeros pasos con ReentrantLock

Comparado con sincronizado, tiene las siguientes características

  • puede ser interrumpido
  • se puede configurar el tiempo de espera
  • Se puede configurar como un candado justo
  • Soporte para múltiples variables de condición

Como sincronizado, ambos admiten reentrada

gramática básica

//获取锁
reetrantLock.lock();
try{
    //临界区
}finally{
    //释放锁
    reentrantLock.unlock();
}

reentrante

Reentrada significa que si un subproceso adquiere el bloqueo por primera vez, es el propietario del bloqueo y tiene derecho a adquirirlo nuevamente. Si se trata de un bloqueo no reentrante, cuando obtenga el bloqueo por segunda vez, el bloqueo también lo bloqueará.

puede ser interrumpido

Si se usa el método lock.lockInterruptably() para bloquear, el método de interrupción puede interrumpir otros subprocesos. Por ejemplo: Mi subproceso a está tratando de adquirir un bloqueo, pero este bloqueo ya ha sido tomado por el subproceso b. Si b ejecuta Interrupción, puede interrumpir directamente la adquisición que el subproceso a está tratando de fallar sin esperar. Es un mecanismo para evitar esperas ilimitadas , evitar esperas muertas y reducir interbloqueos.

tiempo de espera de bloqueo

Hay un método lock.tryLock() que devuelve booleano, devuelve verdadero si se obtiene y devuelve falso si no puede obtener el bloqueo. Este método puede pasar dos parámetros para el tiempo de espera, el primer número de parámetro representa el tiempo y el el segundo es la unidad. Representa la práctica de esperar al máximo cuando va a tryLock() para intentar adquirir el bloqueo. Si es 1 y segundo, intentará esperar como máximo un segundo y devolverá falso antes de obtenerlo. También es un mecanismo de tiempo de espera para evitar interbloqueos.

cerradura justa

Syn es injusto, y la cola bloqueada del monitor de peso pesado es injusta.

ReentrantLock es injusto por defecto , pero podemos cambiarlo para que sea justo a través del método de construcción, pasando el valor booleano

variable de condición

los subprocesos que no cumplen la condición de syn están todos en un salón

Y ReentranLock admite varios salones y se activa según el salón al despertar

manual:

  • await to wait (es necesario adquirir el bloqueo antes)
  • Después de ejecutar await, se liberará el bloqueo y se ingresará conditionObject para esperar
  • El subproceso en espera se despierta (o se interrumpe o se agota el tiempo de espera) para volver a competir por el bloqueo
  • Después de que el bloqueo de la competencia sea exitoso, continúe ejecutando después de esperar

Después del nuevo ReentranLock, puede usar el método newCondition() para crear la sala, y luego puede usar la nueva condición para llamar al método await para ingresar a la sala y esperar. Si se despierta, es el método signal()

2. Principio de AQS

1. Introducción a AQS

Abstract Queued Synchronizer, abstract queue synchronizer, es un bloqueo de bloqueo y un marco de herramientas de sincronizador relacionado, principalmente heredándolo y luego reutilizando sus funciones

Características:

  • El atributo de estado se utiliza para representar el estado del recurso (dividido en modo exclusivo y modelo compartido). Las subclases deben definir cómo mantener este estado y controlar cómo adquirir y liberar bloqueos.
    • getState obtener estado
    • establecer estado establecer estado
    • compareAndSetState: mecanismo cas para establecer el estado del estado (cas evita que múltiples subprocesos modifiquen el estado cuando es seguro para subprocesos)
    • El modo exclusivo es que solo un subproceso puede acceder a los recursos, el modo compartido permite que varios subprocesos accedan a los recursos
  • Proporciona una cola de espera basada en FIFO, similar a EntryList de Monitor
  • Las variables de condición se utilizan para implementar el mecanismo de espera y activación, y se admiten múltiples variables de condición, de forma similar al conjunto de espera del monitor.

Adquisición de bloqueos: tryAcquire(arg) devuelve booleano, y park y unpark se usan para pausar y reanudar en aqs

Liberar el candado: tryRelease(arg) devuelve booleano

2. Cerradura personalizada

El método de bloqueo personalizado se implementa básicamente a través de AQS

  • intente adquirir para modificar el estado, esta vez usando un bloqueo exclusivo, configure el propietario para el bloqueo del objeto AQS
  • Luego está tryRelease, que principalmente establece el estado en 0, desbloquea y libera al propietario.
  • isHeldExcusively: determina si un objeto está ocupando la cerradura
  • newCondition es en realidad el objeto ConditionObject en AQS, que es la creación de variables de condición

Finalmente, el método de implementar el bloqueo es básicamente el método de llamar indirectamente al sincronizador para ejecutar

class MyLock implements Lock {

    //先实现同步器,实现AQS抽象类,他已经帮我们写了大多数方法,我们只需要自定义4个方法
    class MySync extends AbstractQueuedSynchronizer{

        @Override
        protected boolean tryAcquire(int arg) {
             //尝试加锁
             if(compareAndSetState(0,1)){
                 //这个用的是CAS的无锁机制加锁防止多线程安全问题
                 setExclusiveOwnerThread(Thread.currentThread());
                 return true;
             }
             return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            //尝试释放锁
            setExclusiveOwnerThread(null);
            //这个state是volatile修饰,防止指令重排
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            //锁是不是被线程持有
            return getState()==1;

        }

        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    private MySync sync=new MySync();

    @Override//加锁
    public void lock() {
        sync.acquire(1);
    }

    @Override//加锁,可以被中断
    public void lockInterruptibly() throws InterruptedException {
           sync.acquireInterruptibly(1);
    }

    @Override//尝试加锁,不成功就放弃
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    //尝试加锁,超时就进入队列
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        long waitTime = unit.toNanos(time);
        return sync.tryAcquireNanos(1,waitTime);
    }

    @Override
    public void unlock() {
        //他这个释放锁,调用的是release方法,这个方法还会唤醒堵塞队列中的一个线程
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
//        sync.newCondition();
        return sync.newCondition();
    }
}

3. Principio de implementación de ReentrantLock

También tiene una clase de sincronización abstracta en la parte inferior que hereda AQS, y tiene dos implementaciones de bloqueo injusto NonfairSync y bloqueo justo FairSync.

1. Implementación de bloqueo injusto

Proceso de bloqueo

Primero usa compareAndSetState de cas para intentar bloquear. Cuando no hay competencia, si el bloqueo es exitoso, setExclusiveOwnerThread se cambia al hilo actual.

Cuando ocurra la primera competencia, volverá a intentar adquirirse a sí mismo y, si tiene éxito, el bloqueo será exitoso.

Si el bloqueo sigue fallando, se construirá una cola de nodos.La cabeza apunta al primer nodo llamado dummy o centinela, que ocupa un lugar y no está asociado a un hilo.

Luego regrese a un resumen de bucle infinito para seguir intentando adquirir el bloqueo e ingrese al bloque de estacionamiento después de la falla

Si eres el segundo al lado de la cabeza, puedes intentar adquirir de nuevo para intentar adquirir el candado. Cuando el estado sigue siendo 1, falla.

Cambia el nodo frontal, es decir, el estado de espera de la cabeza a -1 (indicando que está a punto de ser bloqueado, y la cabeza está en estado-1, por lo que se encarga de despertarlo más tarde), y esta vez devuelve falso

En este momento, volverá a repetirse y el método de entrada devolverá verdadero porque el nodo precursor es -1, y luego se llamará al método park para bloquear el subproceso actual.

Cuando varios nodos están estacionados y bloqueados, ingresarán uno por uno, y luego el pequeño triángulo sobre cada estado es -1, lo que indica que el nodo precursor es responsable de despertarlo.

Liberar proceso de bloqueo

Intentará Liberar, si tiene éxito, establezca directamente ExclusiveOwnerThread en nulo, luego establezca en 0

Cuando la cola actual no es nula y el estado de espera del encabezado es -1, encontrará el nodo más cercano al encabezado en la cola, el desestacionamiento reanudará su operación y luego ejecutará el método de bloqueo anterior.

Pero este es un bloqueo injusto. Si un subproceso 4 que no está en la cola aparece repentinamente en este momento, competirá con el subproceso que acaba de despertarse. Si 4 obtiene el bloqueo primero y lo establece como exclusivoOwnerThread, entonces 1 volverá -aparcar y entrar bloqueando

2. Principio de reentrada

Tomando bloqueos injustos como ejemplo, adquirir un bloqueo llamará nonfairTryAcquire

Primero juzgará el estado del estado, si es 0, significa que no hay bloqueo, y lo bloqueará directamente.

Si no es 0, significa que está bloqueado, y juzgará si el hilo actual es el mismo que el de ExclusiveOwnerThread, si es el mismo, significa que es reentrante. 

Cuando se libera, se llamará al método tryRelease y se llamará al estado después de la llamada. Si se reduce a 0, se liberará el bloqueo, se establecerá exclusiveOwnerThread en nulo y se dejará que el estado sea 0.

3. Principio interrumpible

modo no interrumpible

El valor predeterminado es no interrumpible . Cuando un subproceso obtiene el bloqueo inmediatamente sin modificaciones, intentará continuamente obtener el bloqueo en un bucle. Si aún falla, ingresará al parque. Al ingresar al parque, otros subprocesos pueden despertarlo. Hay un hilo para interrumpirlo. El valor predeterminado de la bandera de interrupción es falso. Una vez interrumpido, el color se establecerá en verdadero, pero simplemente cambió la bandera y luego volvió a ingresar al parque de bicicletas. Se despierta pero no obtiene el bloqueo o continúa bloqueando, pero hay una bandera verdadera

En el modo no interrumpible, incluso si se interrumpe, seguirá residiendo en la cola AQS y continuará ejecutándose después de obtener el bloqueo. Solo después de obtener el bloqueo sabré que otros subprocesos me han interrumpido (el indicador de interrupción está establecido verdadero)

modo interrumpible

Cuando se llama a doAcquireInterruptible, también es para adquirir el bloqueo, y luego park ingresa a la cola. En este momento, otros subprocesos lo activan para continuar con la ejecución y lanzan directamente InterruptedException, por lo que no hay necesidad de esperar en un bucle , y se puede interrumpir.

4. Principio de bloqueo justo

Un candado injusto no es justo Intente adquirir para adquirir un candado. Al adquirir un candado, si el estado es 0, significa que nadie ha adquirido el candado, y él tomará el candado directamente sin ningún juicio.

Existen múltiples condiciones de juicio para los bloqueos justos.Si todavía hay subprocesos en la cola bloqueada, el bloqueo no se adquirirá.

5. Principio de la variable de condición

esperar proceso

  • El primero es colocar el subproceso en la cola de condición de espera correspondiente (equivalente al salón), la diferencia es que el nodo nulo que está delante del nodo se ha ido y ahora el estado de espera es -2
  • Ejecutará fullRelease y cambiará directamente a 0, que es borrar el candado para evitar el reingreso y la adquisición de otros candados. Luego despierte el hilo de la cola de sincronización. Si la liberación normal es solo -1, el bloqueo reentrante todavía está en el subproceso de propietario exclusivo aunque haya ingresado a la sala de espera.
  • Luego ingrese la condición de bloqueo

Resumen: ingrese a la cola de condiciones, borre el bloqueo y despierte el hilo, y finalmente use el estacionamiento para bloquear.

proceso de señal

  • Primero verifique si el hilo ha adquirido el bloqueo. Solo el titular tiene derecho a despertar. Si no es el titular, se lanzará una excepción y luego se obtendrá el nodo principal de la cola.
  • Luego, llame a doSignal para despertarse primero y unirse a la cola de sincronización (solo después de unirse a Sync puede ingresar a la ejecución de bloqueo de competencia del propietario), similar a ingresar al salón después de esperar, aún debe ingresar a la competencia de cola después de despertarse
  • El siguiente paso es obtener el siguiente nodo (es decir, el nodo de cola condicional real), si es nulo, entonces el último nodo se establece en vacío
  • Si no está vacío, el nodo se elimina y el siguiente nodo se establece en vacío (guardado en firstWaiter)
  • if ((primerCamarero = primero.siguienteCamarero) == null)
  • ultimoCamarero = null;
  • primero.siguienteCamarero = null;
  • En primer lugar, debe saber que esta es una lista enlazada unidireccional y que hay nodos que apuntan al líder del equipo y nodos que apuntan al final del equipo. Puede ver el método addConditionWaiter a continuación. Obviamente, cada vez que el siguiente nodo al final del equipo apunta directamente al nuevo nodo, y luego el final del equipo = nuevo nodo. Tal movimiento. Entonces first.nextWaiter aquí es solo para desconectarse primero, y firstWaiter no está configurado como nulo. Es solo que el puntero no apunta al siguiente nodo. Cada vez es equivalente a mover al líder del equipo firstWaiter a la parte de atrás y luego hacer que el primer nodo espere en la cola de sincronización.
  • Luego es transferir el primer nodo a través del método transferForSignal, y establecer el estado del nodo a 0
  • Nodo p = enq(nodo); Finalmente, el nodo se empalma en la cola de sincronización y se devuelve al nodo predecesor
  • Modificar el estado del nodo predecesor -1. Activación de señal finalizada

Resumen: Signal completa el borrado condicional de la cola (borrado de la lista vinculada de un solo elemento) y luego envía todos los nodos correspondientes a la cola de sincronización. Si falla, puede ser que la cola esté llena o se haya agotado el tiempo de espera. El último paso es eliminar el nodo predecesor y modificar el estado.

Supongo que te gusta

Origin blog.csdn.net/weixin_54232666/article/details/131151006
Recomendado
Clasificación