De semáforo a AQS

Escribir al frente

Aprendizaje de código fuente de semáforo AQS

Aprendizaje de código fuente y demostración

Semáforo semáforo es el límite actual, cada vez que solo se permite ejecutar el hilo que obtiene el semáforo

public static void main(String[] args) {
    
    
   Semaphore semaphore = new Semaphore(2);
   for (int i = 1; i <= 5; i++) {
    
    
       new Thread(()->{
    
    
           try {
    
    
               semaphore.acquire();
               System.out.println(Thread.currentThread().getName()+":aquire 时间:"+System.currentTimeMillis());
               Thread.sleep(1000);
               semaphore.release();
           } catch (InterruptedException e) {
    
    
               e.printStackTrace();
           }
       },"线程" +  i).start();
   }
}

resultado

线程1:aquire 时间:1612068740726
线程2:aquire 时间:1612068740726
线程3:aquire 时间:1612068741727
线程4:aquire 时间:1612068741727
线程5:aquire 时间:1612068742728

Aunque hemos abierto 5 subprocesos, solo se ejecutarán 2 subprocesos cada vez. Debido a que solo hay dos semáforos, solo se ejecutará el subproceso que adquiere el semáforo. Después de que los dos subprocesos actuales adquieran todos los semáforos, el semáforo es 0, y otro el hilo se bloqueará cuando llegue, esperando que otros hilos liberen el semáforo.

Un poco de análisis basado en el código fuente.

semaphore.acquire ();

Obtener el método de semáforo

public Semaphore(int permits) {
    
    
    //信号量构造方法 默认创建非公平锁
    sync = new NonfairSync(permits);
}
 
sync.acquireSharedInterruptibly(1);

public final void acquireSharedInterruptibly(int arg)
         throws InterruptedException {
    
    
     //线程是否中断 如果线程中断过抛出异常
     if (Thread.interrupted())
         throw new InterruptedException();
     //尝试获取共享锁,此处aqs与前面有些不同,前面是独占锁
     //这里记住是共享锁,因为会有多个线程来修改state值
     if (tryAcquireShared(arg) < 0)
         //获取共享锁,获取信号量
         doAcquireSharedInterruptibly(arg);
 }

tryAcquireShared

Primero echemos un vistazo a la implementación de bloqueos injustos y finalmente llamemos al siguiente método

final int nonfairTryAcquireShared(int acquires) {
    
    
    //死循环
     for (;;) {
    
    
         //获取信号量的状态值,按照demo例子,此处为2
         int available = getState();
         //acquires 为 1 , 可用的状态值 - 1
         int remaining = available - acquires;
         //如果值小于0,返回小于0的值
         //如果大于0,设置状态值成功了,返回状态值
         if (remaining < 0 ||
             compareAndSetState(available, remaining))
             return remaining;
     }
 }
  • Loop plus cas sigue siendo la misma rutina, que se utiliza para ejecutar este método correctamente en multiproceso
  • Obtenga el valor de estado, el valor inicial es 2. Si hay 4 subprocesos (ABCD) que ingresan juntos a este método en este momento, el valor restante es 1, pero solo un subproceso que ejecuta compareAndSetState se ejecutará correctamente. Suponiendo que este subproceso cas tiene éxito, Devuelve este 1
  • El resto de los subprocesos continúan en bucle. En este momento, hay 3 subprocesos (BCD) que se ejecutan por segunda vez. En este momento, el resto es igual a 0, todos los cuales son 1-1, y el restante no es menos que 0. En este momento, continúe ejecutando el método compareAndSetState, y solo habrá uno en este momento. El subproceso se ejecuta correctamente, asumiendo que este subproceso B tiene éxito, el valor de estado se establece de 1 a 0 y se devuelve 0
  • Los dos subprocesos restantes (CD) ingresan al tercer ciclo, pero en este momento el estado es 0, cuando el valor es -1 después de restar 1, devuelve directamente -1, y ambos subprocesos devuelven -1
if (tryAcquireShared(arg) < 0)
    doAcquireSharedInterruptibly(arg);
  • Mire el código anterior. Según el ejemplo anterior, uno de los dos primeros subprocesos devuelve 1 y el otro devuelve 0. En lugar de ir al if, regresan directamente y ejecutan el código comercial.
  • Todos los hilos de CD devuelven -1, ingrese el método else

doAcquireSharedInterruptiblemente

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    
    
    //将当前线程加入到CLH队列,加入到队尾
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
    
    
        for (;;) {
    
    
            //获取节点的前置节点是否是头节点,之后head节点的下一个节点才有机会获取到锁,其余节点都需要进行排队等待
            final Node p = node.predecessor();
            if (p == head) {
    
    
                int r = tryAcquireShared(arg);
                if (r >= 0) {
    
    
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //获取当前节点的前置节点,将其状态设置为-1,因为前置节点为-1时才会唤醒后续节点
            //阻塞住当前线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
    
    
        if (failed)
            cancelAcquire(node);
    }
}
  • La lógica principal se ha comentado en el código.
  • Es similar a la lógica del bloqueo reentrante, ¿verdad ?, la diferencia es que cuando se intenta adquirir un bloqueo compartido
  • El código no entrará en detalles. Es aproximadamente el mismo que el bloqueo de reentrada mencionado anteriormente. La inconsistencia es el bloqueo compartido. Veamos la implementación de la subclase del bloqueo compartido.
  • La lógica general es, de acuerdo con el ejemplo anterior, en este momento el subproceso del CD ingresa a este método, primero agrega el subproceso del CD a la cola y establece el atributo para compartir
  • Los hilos de CD entran juntos, pero la cola todavía tiene un orden, si la
    situación actual en la cola es cabeza -> C -> D
  • Ahora CD ingresa en un bucle infinito juntos, primero determine si el nodo frontal del nodo CD es un nodo principal. Obviamente, C es, D no lo es, D va directamente al siguiente método para ingresar al hilo y bloquear, esperando ser despertado
  • El hilo C ingresa si, primero intente obtener el semáforo y vea si algún hilo libera el semáforo, si algún hilo libera el semáforo, obtenga el semáforo y regrese directamente, y vaya al código comercial
  • En este momento, todavía habrá justicia e injusticia. Si C se ejecuta para intentar adquirir el código de bloqueo ( tryAcquireShared ), un hilo E acaba de entrar en este momento, y sucede que el semáforo no es 0. En este momento tiempo, habrá un hilo E y una cola El hilo C compite por este semáforo, y quien tiene éxito en la competencia obtiene el semáforo para la ejecución. Si es justo, E no se adelantará al semáforo con C, y E irá al final de la cola obedientemente. Eso es todo, cabeza -> C -> D -> E

Principalmente nos fijamos en los bloqueos injustos, que es el código que mencionamos anteriormente para obtener bloqueos compartidos.
Inserte la descripción de la imagen aquí

semaphore.release ()

Método de lanzamiento de semáforo

public void release() {
    
    
    sync.releaseShared(1);
}
//释放信号量
public final boolean releaseShared(int arg) {
    
    
    //尝试释放信号量
    if (tryReleaseShared(arg)) {
    
    
        //执行唤醒
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared

protected final boolean tryReleaseShared(int releases) {
    
    
     for (;;) {
    
    
         int current = getState();
         int next = current + releases;
         if (next < current) // overflow
             throw new Error("Maximum permit count exceeded");
         if (compareAndSetState(current, next))
             return true;
     }
 }
  • En este punto, siga el ejemplo anterior para explicar, el hilo AB intenta liberar el bloqueo
  • El bucle infinito más aqs o la situación de subprocesos múltiples pueden ejecutar el código correctamente
  • AB entra al mismo tiempo, obtiene el estado 0, agrega 1 operación y luego usa cas para establecer el estado en 1. Solo un cas tendrá éxito y volverá verdadero después del éxito.
  • Otro hilo ejecuta el siguiente ciclo y establece el estado en 2

Si el estado es mayor que 0, hay un semáforo, ejecute el siguiente código

private void doReleaseShared() {
    
    
    for (;;) {
    
    
        //获取头节点
        Node h = head;
        //说明队列不为空
        if (h != null && h != tail) {
    
    
            //获取状态
            int ws = h.waitStatus;
            //状态等于-1说明需要唤醒后续节点
            if (ws == Node.SIGNAL) {
    
    
                //将当前状态置为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                //唤醒head的下一个节点,同重入锁代码
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        //如果head变了,说明已经有新的线程获取到信号量了,继续循环
        if (h == head)                   // loop if head changed
            break;
    }
}
  • La lógica de este código es despertar los hilos de la cola.
  • Según el ejemplo, el subproceso A libera el semáforo, ejecuta el método de liberación y luego despierta los subprocesos en la cola a través del método anterior
  • Si el encabezado no cambia, significa que el hilo de la cola se ejecuta más tarde que el método actual, si cambia el encabezado, significa que hay un hilo en la cola que ha adquirido el semáforo.

Despertar código

Inserte la descripción de la imagen aquí

  • ¿Cuándo termina el ciclo? Cuando el subproceso A suelta el semáforo para activar el subproceso C en la cola, el subproceso C se bloquea y se despierta, y ejecuta el siguiente ciclo, porque el subproceso C es el siguiente nodo de la cabeza, y el intento de adquirir la cerradura tiene éxito En este momento, el hilo C adquiere Al semáforo, la cabeza cambia.
  • Continuar con el bucle después de los cambios de cabeza
  • Continuar despertando D, el hilo D se bloquea y se despierta, y se ejecuta el siguiente ciclo, porque el hilo D es el siguiente nodo de la cabeza, pero el intento de adquirir el bloqueo falla, el hilo D continúa bloqueándose y la cabeza permanece sin cambios.
  • La cabeza no ha cambiado, el siguiente bucle termina
    Inserte la descripción de la imagen aquí

Lo anterior es el análisis general del código fuente de Semaphore. Si hay algún error, las críticas y correcciones son bienvenidas.

Supongo que te gusta

Origin blog.csdn.net/qq_37904966/article/details/113467852
Recomendado
Clasificación