Explicación práctica del código fuente de AQS

Explicación práctica del código fuente de AQS

I. Resumen

Este artículo tomará ReentrantLockun ejemplo para mostrarle el código fuente de AQS. De hecho, no es difícil. El siguiente es un pequeño caso de bloqueo justo. Puede ejecutarlo y sentirlo usted mismo. Lo siguiente te llevará a leer el código fuente poco a poco, si miras detenidamente, encontrarás que no es difícil.

/**
 * @author VernHe
 * @date 2021年12月02日
 */
public class TestAQS {
    
    
    /**
     * true表示公平锁,false表示非公平锁
     */
    static ReentrantLock reentrantLock = new ReentrantLock(true);

    public static void main(String[] args) {
    
    
        for (int i = 0; i < 5; i++) {
    
    
            new Thread(new Target(i)).start();
        }
    }

    static class Target implements Runnable {
    
    
        // 每个任务的编号
        private int num;

        public Target(int num) {
    
    
            this.num = num;
        }

        @Override
        public void run() {
    
    
            for (int i = 0; i < 2; i++) {
    
    
                reentrantLock.lock();
                try {
    
    
                    System.out.println("任务" + this.num + "执行了");
                } finally {
    
    
                	// unlock方法必须写在finally里面
                    reentrantLock.unlock();
                }
            }
        }
    }
}

En segundo lugar, la parte del código fuente

Bloqueo de reentrada

Mantenga presionado Ctrl+鼠标左键, haga clic en lock()el método, ingresará el método dentro

public void lock() {
    
    
	sync.lock();
}

En este punto, encontrará que se usa el método de sincronización lock() Mantenga presionada la tecla Ctrl y haga clic en sincronizar para encontrar que en realidad es una clase interna estática abstracta

public class ReentrantLock implements Lock, java.io.Serializable {
    
    


    private final Sync sync;

    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
    
    
    	//此处省略
    }
}

A través de los comentarios en inglés (puede traducirlo si no lo entiende), se puede ver que ReentrantLock se basa en la clase Sync para implementar bloqueos justos/injustos, AQSy los stateatributos en uso representan el número de bloqueos. , el AQS que solemos decir es en realidad AbstractQueuedSynchronizer, continúe haciendo clic en lock()el método para bajar, seleccione FairSync, puede ver el siguiente código fuente

static final class FairSync extends Sync {
    
    

	final void lock() {
    
    
		acquire(1);
	}
	// ....省略
}

entrar acquire(1)en

public final void acquire(int arg) {
    
    
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

Aquí hay una explicación de los tres métodos en if:

  • probarAdquirir()

    Como su nombre lo indica, intentará adquirir un bloqueo una vez. La lógica del bloqueo justo/injusto será ligeramente diferente

    protected final boolean tryAcquire(int acquires) {
          
          
    	final Thread current = Thread.currentThread();
        // 获取state的值
    	int c = getState();
    	if (c == 0) {
          
          	// 如果state为0说明目前每人使用
    		if (!hasQueuedPredecessors() &&	// 公平:会判断它前面有没有其他线程,非公平则不会
    			compareAndSetState(0, acquires)) {
          
          	// CAS操作,尝试进行获取
                 setExclusiveOwnerThread(current);	// 把自己设置成独占的线程
                 return true;
              }
         }
         else if (current == getExclusiveOwnerThread()) {
          
          	// 如果有人使用并且使用的人是自己
    		int nextc = c + acquires;	// 每lock()一次就会让state的值增加1
             if (nextc < 0)
                 throw new Error("Maximum lock count exceeded");
             setState(nextc);			// 更新state
             return true;
          }
          return false;
    }
    

    Para resumir, el método anterior es realmente muy simple Solo devolverá verdadero cuando [nadie usa y el hilo actual se monopoliza con éxito] o [el hilo que se está monopolizando es él mismo] .

  • agregarCamarero ()

    La traducción es agregar camareros. De hecho, para decirlo sin rodeos, se agregará a 等待队列la cerradura después de que falle el intento anterior de adquirir la cerradura.

    private Node addWaiter(Node mode) {
          
          
    	Node node = new Node(Thread.currentThread(), mode);	// 为当前线程创建一个节点并设置相应的模式
    	// Try the fast path of enq; backup to full enq on failure
    	Node pred = tail; // 指向等待队列最后面的Node
    	if (pred != null) {
          
          	// 如果有线程也在等待
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
          
          
                pred.next = node; // 排到最后一个Node的后面
                return node; // 排队成功后返回当前线程对应的Node
            }
    	}
        enq(node); // 如果本线程是第一个排队的,或者前面排队没成功,则再次尝试排队直至成功为止
        return node; // 排队成功后返回当前线程对应的Node
    }
    

    En resumen, para ponerlo en términos sencillos, es hacer cola

  • adquirirEnCola()

    Al hacer giro CAS en esto, el bloqueo se adquirirá continuamente y el subproceso actual se bloqueará después de una falla.

    final boolean acquireQueued(final Node node, int arg) {
          
          
    	boolean failed = true;	// 记录是否独占失败
    	try {
          
          
            boolean interrupted = false; // 记录线程是否被打断
            for (;;) {
          
          	// 循环,直至成功独占
                final Node p = node.predecessor(); // 获取前一个Node
                if (p == head && tryAcquire(arg)) {
          
          	// 如果自己是第二个并且成功独占
                    setHead(node);	// 把当前Node设置成新的head
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;	// 返回,跳出循环
                }
                if (shouldParkAfterFailedAcquire(p, node) && //检查自己是否应该阻塞
                    parkAndCheckInterrupt()) // 阻塞当前线程(debug会停在这一步)
                    interrupted = true;	 // 当线程被重新唤醒时才会知心这个方法,然后继续循环
    		}
    	} finally {
          
          
    		if (failed)
    			cancelAcquire(node); // 如果独占失败,则会唤醒后面的Node继续执行此方法
    	}
    }
    

AbstractQueuedSynchronizerAbstractQueuedSynchronizer

componente

1, 等待队列(CLH队列)de hecho, es esencialmente una lista doblemente enlazada

2. stateVariables, guarda el estado de sincronización

3, heady el tailpuntero , utilizado para guardar la cabeza y la cola de la cola de espera

4. La clase interna Node, a través de dos punteros, el nodo predecesor y el nodo sucesor

5. Métodos de operación de colas y una serie de CASmétodos nativos

3. Resumen

Personalmente, el marco AQS extrae muchas características de las estrategias de sincronización, como semáforos, mutexes y varios bloqueos que pueden requerir que algunos subprocesos esperen. Por esta razón, se extrae una cola de bloqueo (CLH) para su uso. Para guardar un subproceso bloqueado y úsalo para despertarte más tarde. Diferentes estrategias de sincronización permiten diferentes números de subprocesos que se ejecutan simultáneamente, por lo que se extrae una variable de estado. Después de eso, hay una serie de métodos CAS para operar la cola de bloqueo, y la capa inferior es la operación atómica implementada por el lenguaje C.

Supongo que te gusta

Origin blog.csdn.net/weixin_44829930/article/details/121708602
Recomendado
Clasificación