Praktische Erklärung des AQS-Quellcodes

Praktische Erklärung des AQS-Quellcodes

I. Überblick

​ Dieser Artikel ReentrantLockzeigt Ihnen anhand eines Beispiels den Quellcode von AQS. Tatsächlich ist es nicht schwierig. Das Folgende ist ein kleiner Fall von Fair Lock. Sie können es ausführen und selbst fühlen. Im Folgenden werden Sie den Quellcode Stück für Stück lesen, und wenn Sie genau hinschauen, werden Sie feststellen, dass es nicht schwierig ist.

/**
 * @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();
                }
            }
        }
    }
}

Zweitens der Quellcodeteil

ReentrantLock

Drücken und halten Ctrl+鼠标左键Sie , klicken Sie lock()auf die Methode, Sie werden die Methode darin eingeben

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

An diesem Punkt werden Sie feststellen, dass die lock()-Methode der Synchronisierung verwendet wird. Halten Sie Strg gedrückt und klicken Sie auf Synchronisierung, um festzustellen, dass es sich tatsächlich um eine abstrakte statische innere Klasse handelt

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 {
    
    
    	//此处省略
    }
}

Durch die englischen Kommentare (Sie können es übersetzen, wenn Sie es nicht verstehen) ist ersichtlich, dass ReentrantLock auf der Sync-Klasse basiert, um faire/unfaire Sperren zu implementieren, AQSund die stateverwendeten Attribute die Anzahl der Sperren darstellen , das AQS, das wir oft sagen, ist eigentlich AbstractQueuedSynchronizer, klicken Sie weiter auf lock()die Methode, um nach unten zu gehen, wählen FairSyncSie , Sie können den folgenden Quellcode sehen

static final class FairSync extends Sync {
    
    

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

eingehen acquire(1)_

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

Hier ist eine Erklärung der drei Methoden in if:

  • tryAcquire()

    Wie der Name schon sagt, wird es versuchen, eine Sperre einmal zu erwerben. Die Logik des fairen/unfairen Sperrens wird etwas anders sein

    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;
    }
    

    Zusammenfassend lässt sich sagen, dass die obige Methode eigentlich sehr einfach ist: Sie gibt nur dann true zurück, wenn [niemand verwendet und der aktuelle Thread erfolgreich monopolisiert wird] oder [der Thread, der gerade monopolisiert wird, er selbst ist] .

  • hinzufügenKellner ()

    Die Übersetzung besteht darin, Kellner hinzuzufügen.In der Tat, um es unverblümt auszudrücken, es wird 等待队列der Sperre hinzugefügt, nachdem der obige Versuch, die Sperre zu erwerben, fehlschlägt.

    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
    }
    

    Um es zusammenzufassen, um es in Laienbegriffen auszudrücken, heißt es anstehen

  • erwerbenQueued()

    Wenn Sie in diesem CAS-Spin ausführen, wird die Sperre kontinuierlich abgerufen und der aktuelle Thread wird nach einem Fehler blockiert.

    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继续执行此方法
    	}
    }
    

AbstractQueuedSynchronizer

Komponente

​ 1, 等待队列(CLH队列)tatsächlich handelt es sich im Wesentlichen um eine doppelt verkettete Liste

2. stateVariablen, speichern den Synchronisationszustand

​ 3 headund tailZeiger , werden verwendet, um den Kopf und den Schwanz der Warteschlange zu speichern

4. Die innere Klasse Node, durch zwei Zeiger, den Vorgängerknoten und den Nachfolgerknoten

5. Methoden zum Betreiben von Warteschlangen und eine Reihe CASnativer Methoden

3. Zusammenfassung

Persönlich extrahiert das AQS-Framework viele Funktionen von Synchronisationsstrategien, wie Semaphore, Mutexe und verschiedene Sperren, die möglicherweise das Warten einiger Threads erfordern. Aus diesem Grund wird eine blockierende Warteschlange (CLH) extrahiert, um einen blockierten Thread zu speichern und verwenden Sie es, um später aufzuwachen. Da unterschiedliche Synchronisationsstrategien unterschiedliche Anzahlen gleichzeitig laufender Threads zulassen, wird eine Zustandsvariable extrahiert. Danach gibt es eine Reihe von CAS-Methoden, um die blockierende Warteschlange zu betreiben, und die unterste Schicht ist die atomare Operation, die von der Sprache C implementiert wird.

Ich denke du magst

Origin blog.csdn.net/weixin_44829930/article/details/121708602
Empfohlen
Rangfolge