Maison  >  Article  >  Java  >  Introduction au principe d'implémentation de ReentrantLock (exemple de code)

Introduction au principe d'implémentation de ReentrantLock (exemple de code)

不言
不言avant
2019-01-31 11:14:032963parcourir

Cet article vous apporte une introduction aux principes de mise en œuvre de ReentrantLock (exemples de code). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Dans la programmation simultanée, en plus du mot-clé synchronisé, ReentrantLock et ReentrantReadWriteLock dans java.util.concurrent.locks dans le package de concurrence Java sont également des implémentations de verrouillage couramment utilisées. Cet article analyse le principe du verrouillage réentrant à partir du code source.

Parlons d'abord des verrous réentrants : une fois qu'un thread a obtenu le verrou, il peut l'obtenir plusieurs fois sans se bloquer.

ReentrantLock est implémenté sur la base de la classe abstraite AbstractQueuedSynchronizer (ci-après dénommée AQS).

Regardez le code source :

Tout d'abord, le constructeur montre que ReentrantLock a deux mécanismes : le verrouillage équitable et le verrouillage injuste.

//默认非公平锁
public ReentrantLock() {
        sync = new NonfairSync();
    }

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

Expliquez d'abord brièvement la différence entre les verrous équitables et les verrous injustes, puis analysez les différentes méthodes de mise en œuvre des deux.

Verrouillage équitable : les discussions multiples sont du premier arrivé, premier servi. Semblable à la file d'attente, les threads arrivant plus tard sont placés à la fin de la file d'attente.

Verrouillage injuste : rivalisez pour les verrous. S'il est saisi, il sera exécuté. S'il n'est pas saisi, il sera bloqué. Attendez que le fil de discussion qui a acquis le verrou soit libéré avant de participer au concours.

Des verrous injustes sont donc généralement utilisés. Son efficacité est supérieure à celle du verrouillage équitable.

Acquérir le verrouillage

Verrouillage équitable

final void lock() {
            acquire(1);
        }

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

Première étape tryAcquire(arg) Try à verrouiller, implémenté par FairSync. Le code spécifique est le suivant :

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

  • Obtenir le fil de discussion actuel

  • Obtenir l'état dans AQS. Si l'état est 0, cela signifie qu'aucun thread n'a obtenu le verrou pour le moment. ​

  • Dans le jugement if, il faut d'abord juger si la file d'attente du nœud AQS est vide. S'il n'est pas vide, vous devrez faire la queue. Le verrou n'est pas acquis pour le moment.

  • Essayez d'utiliser l'algorithme CAS pour mettre à jour l'état à 1. La mise à jour réussit, le verrou est acquis et le thread à ce moment est défini sur le thread exclusif exclusiveOwnerThread. Renvoie vrai.

  • Si l'état n'est pas 0, cela signifie qu'un thread a déjà obtenu le verrou. Il est donc nécessaire de déterminer si le thread qui a obtenu le verrou (thread exclusif) est le thread actuel.

  • Si oui, cela signifie réentrée. Augmente l'état de 1. Renvoie vrai.

  • A la dernière étape, le verrou n'est pas obtenu. Return false;

Continuez avec les étapes ci-dessus si l'acquisition du verrou échoue, exécutez d'abord addWaiter(Node.EXCLUSIVE) et écrivez le thread actuel dans la file d'attente

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;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

  • Encapsuler un nouveau nœud de nœud

  • Déterminer si la fin de la liste chaînée est vide, et sinon, écrivez le nouveau nœud node' Enfin

  • ' si la fin de la liste chaînée est vide, utilisez enq(node) pour l'écrire jusqu'à la fin.

Après avoir écrit dans la file d'attente, la méthode acquireQueued() suspend le thread en cours.

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

  • Dans la boucle, si le nœud précédent est le nœud principal, essayez à nouveau d'acquérir le verrou. En cas de succès, la boucle se termine et false est renvoyé.

  • n'est pas le nœud principal. En fonction du waitStatus du nœud précédent, il est jugé si le thread actuel doit être suspendu. waitStatus est utilisé pour enregistrer l'état du nœud, tel que l'annulation du nœud, l'attente du nœud, etc.

  • Si vous déterminez qu'il doit être suspendu, utilisez la méthode parkAndCheckInterrupt() pour suspendre le thread. Plus précisément, utilisez LockSupport.park(this) pour suspendre le thread.

  • Si l'acquisition du verrou réussit dans la première étape ici, vous pouvez annuler l'opération d'acquisition du verrou pour ce nœud.

Verrouillage injuste

Le verrouillage injuste présente des différences dans la stratégie d'acquisition du verrou.

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

  • Le verrou injuste tente d'abord directement d'utiliser l'algorithme CAS pour mettre à jour l'état et acquérir le verrou

  • Après l'échec de la mise à jour, après avoir tenté d'acquérir le verrou

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

Par rapport à la foire lock, le verrou injuste tente d'acquérir le verrou. Pendant le processus, il n'est pas nécessaire de déterminer s'il y a d'autres threads dans la file d'attente.

Libérez le verrou

Les étapes pour déverrouiller le verrou sont les mêmes pour le verrouillage équitable et le verrouillage injuste

public void unlock() {
        sync.release(1);
    }
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
//更新state
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

Il convient de noter que, comme il s'agit d'un verrou réentrant, dans la méthode tryRelease(), l'état doit être mis à jour à 0 avant que le verrou soit considéré comme complètement libéré. Après avoir relâché, réveillez le fil suspendu.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer