Galleria mappe mentale Strumento di blocco della concorrenza Java
Questa è una mappa mentale sugli strumenti di blocco della concorrenza Java. Questi strumenti di blocco della concorrenza possono svolgere un ruolo importante nella programmazione simultanea di Java e aiutarti a scrivere codice simultaneo efficiente e sicuro.
Modificato alle 2024-01-18 10:28:15Questa è una mappa mentale su una breve storia del tempo. "Una breve storia del tempo" è un'opera scientifica popolare con un'influenza di vasta portata. Non solo introduce i concetti di base della cosmologia e della relatività, ma discute anche dei buchi neri e dell'espansione dell'universo. questioni scientifiche all’avanguardia come l’inflazione e la teoria delle stringhe.
Dopo aver letto "Il coraggio di essere antipatico", "Il coraggio di essere antipatico" è un libro filosofico che vale la pena leggere. Può aiutare le persone a comprendere meglio se stesse, a comprendere gli altri e a trovare modi per ottenere la vera felicità.
"Il coraggio di essere antipatico" non solo analizza le cause profonde di vari problemi nella vita, ma fornisce anche contromisure corrispondenti per aiutare i lettori a comprendere meglio se stessi e le relazioni interpersonali e come applicare la teoria psicologica di Adler nella vita quotidiana.
Questa è una mappa mentale su una breve storia del tempo. "Una breve storia del tempo" è un'opera scientifica popolare con un'influenza di vasta portata. Non solo introduce i concetti di base della cosmologia e della relatività, ma discute anche dei buchi neri e dell'espansione dell'universo. questioni scientifiche all’avanguardia come l’inflazione e la teoria delle stringhe.
Dopo aver letto "Il coraggio di essere antipatico", "Il coraggio di essere antipatico" è un libro filosofico che vale la pena leggere. Può aiutare le persone a comprendere meglio se stesse, a comprendere gli altri e a trovare modi per ottenere la vera felicità.
"Il coraggio di essere antipatico" non solo analizza le cause profonde di vari problemi nella vita, ma fornisce anche contromisure corrispondenti per aiutare i lettori a comprendere meglio se stessi e le relazioni interpersonali e come applicare la teoria psicologica di Adler nella vita quotidiana.
Strumento di blocco della concorrenza Java
AbstractQueueSynchronizer
introduzione
Il framework di base per la creazione di serrature o altri componenti di sincronizzazione, utilizzato principalmente per gestire lo stato di sincronizzazione
Il modo principale per usarlo è l'ereditarietà
Il blocco può verificarsi solo in un momento, riducendo il costo del cambio di contesto.
ereditare
AbstractOwnableSynchronizer
Implementazione di base del sincronizzatore che consente ai thread di occupare la modalità esclusiva
Variabili membro
Thread transitorio privato ExclusiveOwnerThread
Il thread che attualmente occupa lo stato di sincronizzazione
metodo principale
set void finale protettoExclusiveOwnerThread(thread del thread)
Imposta thread esclusivo
Discussione finale protetta getExclusiveOwnerThread()
Ottieni l'attuale thread esclusivo
implementare l'interfaccia
Serializzabile
classe interiore
Nodo di classe finale statica
introduzione
AQS utilizza internamente una coda di sincronizzazione FIFO bidirezionale per completare la gestione dello stato di sincronizzazione. Quando un thread non riesce a ottenere, viene aggiunto alla fine della coda. La struttura della coda è Nodo, i nodi testa e coda sono definiti nell'AQS. Anche la coda delle condizioni utilizza la definizione del nodo, ma si trova in un'altra coda
Variabili membro
costante statica
Nodo finale statico CONDIVISO = nuovo Nodo()
Indica che il nodo è in modalità condivisa
Nodo finale statico EXCLUSIVE = null
Indica che il nodo è in modalità esclusiva e solo un thread mantiene lo stato di sincronizzazione contemporaneamente.
static final int CANCELLATO = 1
Il nodo (thread) è in questo stato a causa di timeout o interruzione e non cambierà successivamente in altri stati, né dovrebbe più essere bloccato.
statico finale int SEGNALE = -1
Quando il nodo (thread) è in questo stato, il nodo successore è o sarà in uno stato bloccato (tramite park), quindi quando il nodo corrente rilascia il blocco o viene annullato, è necessario chiamare unpark per riattivare il nodo successore .
statico finale int CONDIZIONE = -2
Indica che il nodo corrente (thread) è nella coda delle condizioni (tramite wait) e non fa parte della coda di sincronizzazione finché lo stato non viene impostato su 0 in un determinato momento (tramite segnale)
static final int PROPAGATA = -3
Propagare i lock condivisi (utilizzati dal nodo head)
volatile int waitStatus
Includendo quanto sopra CANCELLATO/SEGNALE/CONDIZIONE/PROPAGATA e 0 (che indica che non è in questi quattro stati), un numero non negativo indica che il nodo non richiede interazione
Nodo volatile prec
Nodo precursore
Nodo volatile successivo
nodo successore
thread thread volatile
Il thread corrispondente al nodo
Nodo successivoCameriere
Nella coda di sincronizzazione indica se il nodo è in uno stato condiviso o esclusivo, uguale a SHARED per indicare condivisione, e null per indicare esclusivo nella coda condizionale indica un riferimento al nodo successivo.
La condizione può essere utilizzata solo quando la coda di sincronizzazione è in modalità esclusiva, quindi questa variabile è progettata per essere condivisa.
metodo principale
Metodo di costruzione
Nodo()
Utilizzato per stabilire nodi iniziali o costruire nodi CONDIVISI
Nodo (thread thread, modalità nodo)
Da utilizzare in addWaiter
Nodo (thread thread, int waitStatus)
Usato in condizioni
metodo dei membri
booleano finale isShared()
return nextCameriere == CONDIVISO
nodo finale predecessore()
Ottieni il nodo predecessore, genera un'eccezione quando è nullo
classe pubblica ConditionObject
introduzione
L'implementazione dell'interfaccia Condition serve all'implementazione Lock di base. Utilizza Node per costruire una coda di attesa. Ciascun oggetto Condizione contiene una coda FIFO (unidirezionale).
La coda utilizza la proprietà nextWaiter del nodo come riferimento al nodo successivo.
implementare l'interfaccia
Condizione
Serializzabile
Variabili membro
costante statica
private static final int REINTERRUPT = 1
Indica la necessità di interrompere nuovamente quando si esce dall'attesa
private static final int THROW_IE = -1
Indica che è necessario generare InterruptedException quando si esce dall'attesa
nodo transitorio privato firstWaiter
Punta al nodo head della coda condizionale
Nodo transitorio privato lastWaiter
Punta al nodo di coda della coda delle condizioni
metodo principale
annullamento finale pubblico attendono()
Fai in modo che il thread chiamante entri nella coda di attesa, rilasci il blocco e entri nello stato di blocco. Dopo che il nodo è stato risvegliato da signal/signalAll, prova ad acquisire il blocco. Se il blocco viene risvegliato da un'interruzione, risponderà all'interruzione.
Nodo privato addConditionWaiter()
Aggiungi un nuovo nodo alla coda di attesa.
Logica di implementazione specifica: quando viene rilevato che lastWaiter è annullato, chiamare unlinkCancelledWaiters per cancellare i metodi annullati nell'intera coda (sia firstWaiter che lastWaiter possono puntare a nuovi nodi, quindi creare un nuovo nodo (stato CONDITION) e aggiungerlo al file fine della coda
private void unlinkCancelledWaiters()
Utilizzare while per cancellare i nodi il cui waitStatus non è CONDITION da firstWaiter in poi. Poiché la chiamata al metodo avviene prima che il blocco venga rilasciato, non è necessario che il processo del ciclo sia bloccato. Per evitare residui nel GC, quando non c'è segnale, questo metodo viene utilizzato solo quando si verifica il timeout o l'annullamento.
Il waitStatus del nodo nella coda delle condizioni dovrebbe avere solo due stati: CONDITION e CANCELLED.
final int FullyRelease(Nodo nodo)
Rilascia lo stato di blocco Se il rilascio dello stato di blocco non riesce, verrà generata un'eccezione IllegalMonitorStateException e il nodo verrà impostato sullo stato CANCELED. In caso di successo, verrà restituito il valore dello stato prima del rilascio.
pubblicazione
Qui viene richiamato il metodo di rilascio di AQS e viene trovato un nodo adatto da riattivare.
I parametri utilizzano l'attributo state di AQS
booleano finale isOnSyncQueue(Nodo nodo)
Determina se il nodo è nella coda di sincronizzazione (nota che questa è una coda di sincronizzazione anziché una coda condizionale): quando waitStatus è CONDIZIONE, significa che non è nella coda di sincronizzazione Se lo è il pre o il successivo del nodo null, significa anche che non è nella coda di sincronizzazione (questi due attributi vengono utilizzati per la coda di sincronizzazione. Il predecessore e il successore della coda condizionale non utilizzano questi due attributi), quindi chiama il metodo findNodeFromTail per attraversare la sincronizzazione coda dal nodo coda per vedere se il nodo è al suo interno.
La relazione tra coda di sincronizzazione e coda di condizioni: la competizione dei blocchi si basa solo sulla coda di sincronizzazione. La coda di condizioni salva solo i thread bloccati per mancanza di condizioni. Quando i thread devono competere per i blocchi, devono comunque essere convertiti in coda di sincronizzazione.
Questo metodo viene utilizzato come condizione di giudizio del while nel codice. Se il nodo non è nella coda di sincronizzazione (indicando che non è signal/signalAll), entrerà nel while e si bloccherà tramite park. Quando il nodo viene risvegliato, determinerà innanzitutto se è stato risvegliato da un'interruzione. In caso contrario, continuerà a tornare al processo while. Altrimenti, tenterà di aggiungere il nodo alla coda di sincronizzazione e di uscire dal processo while processi.
captureQueued(nodo, stato salvato)
Tentativo costante di ottenere lo stato di sincronizzazione saveState per i nodi nella coda di sincronizzazione
scollegareCancellatoCamerieri
Dopo aver acquisito il blocco, se node.nextWaiter non è null, chiama questo metodo per cancellare il nodo dallo stato annullato.
reportInterruptAfterWait
Se in precedenza è stato risvegliato a causa di un'interruzione, l'interruzione deve essere elaborata in base ai risultati del giudizio precedente, se ripristinare lo stato di interruzione o generare un'eccezione di interruzione.
pubblico finale lungo waitNanos(long nanosTimeout)
La logica di base è simile all'attesa, con l'aggiunta del giudizio di timeout.
Non capisco bene l'ultima parte di transferAfterCancelledWait.
attesa booleana finale pubblica (molto tempo, unità TimeUnit)
La logica di base è simile a waitNanos, puoi specificare l'unità di tempo e alla fine viene convertita in nanosecondi per il calcolo.
public final void attendonoUninterruptibly()
La logica di base è simile all'attesa e non risponde agli interrupt (ovvero, selfInterrupt ripristina lo stato di interruzione)
public final booleano waitUntil(Data di scadenza)
La logica di base è simile all'attesa, con l'aggiunta di un tempo massimo di blocco.
segnale pubblico di annullamento finale()
Sposta il thread in attesa più lungo dalla coda condizionale alla coda di sincronizzazione
booleano protetto isHeldExclusively()
Restituisce se il thread corrente mantiene il blocco in modalità esclusiva, in tal caso restituisce true. Questo metodo è in AQS e non fornisce un'implementazione specifica. Tuttavia, poiché questo metodo viene utilizzato solo nella condizione, non è necessario implementarlo se ConditionObject non viene utilizzato, altrimenti deve essere implementato.
private void doSignal (prima il nodo)
Rimuovi i nodi dalla coda delle condizioni e convertili in nodi dalla coda di sincronizzazione. Il processo di implementazione è un loop, da davanti a dietro, finché non incontra un nodo che non è nullo e non nello stato CANCELED, convertilo ed esci dal loop
trasferimento booleano finaleForSignal(Nodo nodo)
Converti il nodo dalla coda condizionale alla coda di sincronizzazione e restituisci se la conversione ha avuto esito positivo. La logica di implementazione consiste nel determinare innanzitutto se lo stato del nodo è CONDITION Se non è un nodo annullato, restituire false; quindi chiamare enq per accedere alla coda di sincronizzazione e ottenere il nodo predecessore del nodo nella coda lo stato del nodo predecessore è > 0 o il CAS viene modificato Se waitStatus fallisce (si sono verificate modifiche), annulla il parcheggio del thread e infine restituisce true
Non si riattiva necessariamente direttamente dopo essersi unito alla coda di sincronizzazione. Solo quando waitStatus>0 o modifiche durante l'esecuzione CAS il thread verrà annullato il parcheggio e riattivato. Se non ti svegli in questo momento, attenderai fino alla logica della coda di sincronizzazione e poi ti sveglierai.
enq
segnale pubblico di annullamento finaleTutto()
Trasferisci tutti i nodi qualificati (non annullati) nella coda delle condizioni nella coda di sincronizzazione. La logica di base è simile a quella di signal, la differenza è che il metodo transferForSignal viene eseguito in loop
è tenuto in esclusiva
private void doSignalAll (prima il nodo)
transferForSignal
protetto booleano finale hasWaiters()
Indipendentemente dal fatto che siano presenti thread nella coda delle condizioni, il metodo di implementazione consiste nel scorrere la coda delle condizioni dall'inizio. Se è presente un nodo il cui stato è CONDIZIONE, restituisce true, altrimenti restituisce false.
Variabili membro
variabile statica
spinForTimeoutThreshold lungo finale statico = 1000L;
La soglia utilizzata per il timeout. Se è inferiore a questo valore, non è necessario chiamare il parcheggio con timeout, ma lasciare che il ciclo continui l'esecuzione. In questo momento, l'efficienza della rotazione è maggiore
privato statico finale Non sicuro non sicuro
Unsafe.getUnsafe(), i seguenti sono tutti gli offset ottenuti tramite il metodo objectFieldOffset, che facilita le successive modifiche direttamente tramite l'operazione CAS di Unsafe.
privato statico finale lungo stateOffset
Stato AQS
headOffset lungo finale statico privato
Responsabile dell'AQS
coda lunga finale statica privataOffset
Coda AQS
privato statico finale lungo waitStatusOffset
WaitStatus del nodo
privato statico finale lungo nextOffset
Nodo successivo
Testa del nodo volatile transitoria privata
Il nodo head della coda di sincronizzazione, lazyload, modificato solo tramite setHead quando esiste il nodo head, il suo stato non può essere ANNULLATO;
coda del nodo volatile transitoria privata
Il nodo di coda della coda di sincronizzazione, caricamento lento
privato volatile int stato
Stato della sincronizzazione
metodo principale
protetto final int getState()
Restituisce il valore corrente dello stato di sincronizzazione
protetto final void setState(int newState)
Imposta lo stato di sincronizzazione corrente
protetto finale booleano compareAndSetState(int aspetta, int aggiornamento)
CAS imposta lo stato e il livello inferiore chiama il metodo CAS di Unsafe.
Nodo privato addWaiter(modalità nodo)
metodo di accodamento
Aggiunge il thread corrente alla fine della coda utilizzando la modalità specificata
Vale la pena notare che
Il parametro Node viene utilizzato per specificare la modalità e il thread viene ottenuto tramite currentThread
Nell'implementazione, quando la coda non è vuota, prova rapidamente cas a impostare la coda. In caso di successo, verrà restituita direttamente. Se fallisce, verrà chiamato il metodo enq.
nodo privato enq(nodo nodo finale)
Loop (spin) CAS Quando tail è vuoto, CAS inizializza prima head (nuovo nodo). Quando tail non è vuoto, CAS imposta tail.
Il parametro node qui è il nodo che deve essere aggiunto anziché la modalità.
booleano finale pubblico hasQueuedPredecessors()
Determina se esiste un nodo prima del thread corrente, in tal caso restituisce true, altrimenti restituisce false. Utilizzato per la realizzazione di serrature giuste. Implementazione specifica: quando la coda non è vuota, restituisce true se si ritiene che il nodo successivo di head sia vuoto o non sia il thread corrente.
Quando head.next è nullo?
booleano finale pubblico hasQueuedThreads()
return head != tail; Se ci sono thread in attesa in coda
public final booleano isQueued(thread)
Se il thread è in coda, l'implementazione prevede il loop dalla fine all'inizio
public final int getQueueLength()
Ottieni la lunghezza della coda. Il metodo di implementazione consiste nel ripetere il ciclo dalla fine all'inizio per calcolare il numero di nodi il cui thread non è nullo.
Collezione finale pubblica<Thread> getQueuedThreads()
Ottiene la raccolta di thread in coda e la restituisce sotto forma di Collection. L'implementazione consiste nel costruire un ArrayList, eseguire un ciclo dalla fine all'inizio per aggiungere thread che non siano null e quindi restituire l'oggetto ArrayList.
proprietà booleana finale pubblica (condizione ConditionObject)
Se l'oggetto condizione appartiene al sincronizzatore AQS corrente
modalità esclusiva
Solo un thread può ottenere lo stato di sincronizzazione allo stesso tempo
public final void acquire(int arg)
La modalità esclusiva ottiene lo stato di sincronizzazione e ignora le interruzioni; chiamare prima tryAcquire per provare ad acquisire il blocco esclusivo e, in caso di errore, chiamare acquireQueued(addWaiter(Node.EXCLUSIVE), arg) per provare ad aggiungere il thread alla coda di sincronizzazione e determinare il stato del nodo e quindi in base al risultato restituito (se è presente (Interruzione del thread) Chiamare il metodo di interruzione del thread per ripristinare lo stato di interruzione
L'acquisizione stessa non risponde agli interrupt, quindi l'elaborazione degli interrupt serve a ripristinare lo stato di interrupt
booleano protetto tryAcquire(int arg)
Prova a ottenere un blocco esclusivo, restituisce true in caso di successo, false in caso di fallimento. L'implementazione specifica è completata da sottoclassi e la sicurezza del thread deve essere garantita quando si ottiene lo stato di sincronizzazione.
final booleano acquireQueued(final Node node, int arg)
arg può essere inteso come una risorsa che deve essere competitiva. AQS è rappresentato internamente dallo stato, ma in realtà l'uso specifico è definito dallo sviluppatore.
Prova continuamente ad acquisire lock per i thread già in coda (il nodo è stato creato da addWaiter)
Logica di implementazione: Spin come segue: Se il nodo predecessore è head e acquisisce il lock con successo, impostare il nodo corrente su head e il ritorno non viene interrotto, altrimenti procedere con le seguenti operazioni: chiamare ShouldParkAfterFailedAcquire per determinare e impostare lo stato del nodo e chiamare parkAndCheckInterrupt per bloccare il thread e controllare lo stato di interruzione. Se si verifica un'eccezione durante il processo, chiamare cancelAcquire per annullare l'acquisizione.
Infatti, anche se il lock non viene acquisito, il loop non si fermerà. Quando lo stato del nodo predecessore viene impostato con successo e il thread viene interrotto con park, il thread viene sospeso; Il nodo dopo head utilizzerà tryAcquire per competere con il nuovo thread per il lock, indicando che l'implementazione del lock qui è ingiusta.
private void setHead(Nodo nodo)
Imposta il nodo come head e imposta gli attributi thread e prev su null per facilitare GC. Di fatto equivale a staccarsi dalla coda.
booleano statico privato dovrebbeParkAfterFailedAcquire(Node pred, Node node)
Controlla e aggiorna lo stato del nodo predecessore del nodo che non è riuscito ad acquisire il blocco su SIGNAL e restituisce se il thread corrente deve essere bloccato.
Logica di implementazione: determina il waitStatus del nodo predecessore e restituisce true quando è SIGNAL quando > 0, significa che il nodo predecessore è stato cancellato e il nodo predecessore verrà saltato in un ciclo fino al waitStatus di un nodo predecessore; <=0, quindi restituisce false; altrimenti CAS Imposta il waitStatus del nodo predecessore su SIGNAL e restituisce false
privato finale booleano parkAndCheckInterrupt()
Parcheggia la chiamata, controlla lo stato dell'interruzione e ritorna
return thread.interrupted() Il risultato restituito qui viene utilizzato per determinare se il thread è stato risvegliato a causa di un'interruzione. Se è stato risvegliato a causa di un'interruzione, sarà ancora nel ciclo in acquireQueued e verrà nuovamente bloccato da park (. perché il metodo interrotto() cancellerà il flag di interruzione, quindi park potrà avere nuovamente effetto); quando risvegliato da unpark, il blocco verrà acquisito quando pseudo-risvegliato, basta controllare nuovamente attraverso il ciclo esterno
Cosa causa la falsa eccitazione?
private void cancelAcquire(Nodo nodo)
Chiamato quando l'intero metodo lancia un'eccezione, svuota il thread dal Nodo, imposta lo stato su CANCELLED e trova il nodo con waitStatus<=0 come precursore rimuoviti dalla coda e sveglia quello successivo se necessario; un nodo adatto
private void unparkSuccessor(Nodo nodo)
Da notare che se il next del nodo non è vuoto, unpark esso se il next del nodo è null, inizia dalla coda e trova il nodo più in primo piano con waitStatus<=0 che non sia il nodo dalla coda a; la parte anteriore
Il motivo per cui cerchiamo da dietro in avanti è perché l'impostazione del predecessore e del successore di una lista doppiamente collegata non è un'operazione atomica. Può succedere che il successivo di un nodo sia vuoto ma sia già in coda, oppure che il nodo abbia appena stato cancellato e il successivo punta a se stesso e l'attraversamento arriva proprio a questo nodo
public final void acquireInterruptibly(int arg)
Per ottenere lo stato di sincronizzazione in modalità esclusiva in risposta all'interruzione, verrà utilizzato Thread.interrupted() per determinare se viene interrotto prima di tryAcqure. In tal caso, verrà generata un'eccezione.
provaAcquisisci
provalo una volta prima
private void doAcquireInterruptibly(int arg)
La logica di base è la stessa di acquireQueued La differenza è che addWaiter viene chiamato internamente e non restituisce se c'è un'interruzione del thread, ma lancia direttamente un'InterruptedException dopo aver verificato che il parco sia stato risvegliato dall'interruzione.
annullaAcquisisci
public final booleano tryAcquireNanos(int arg, long nanosTimeout)
Acquisizione esclusiva dello stato di sincronizzazione con timeout, rispondendo agli interrupt. Se lo stato di sincronizzazione non viene ottenuto entro il periodo di timeout, viene restituito false.
provaAcquisisci
provalo una volta prima
booleano privato doAcquireNanos(int arg, long nanosTimeout)
La logica di base è la stessa di doAcquireInterruptibly, con l'aggiunta della logica del giudizio temporale e l'uso della soglia spinForTimeoutThreshold.
annullaAcquisisci
rilascio booleano finale pubblico(int arg)
La modalità esclusiva rilascia lo stato di sincronizzazione. Per prima cosa chiama tryRelease per provare a modificare lo stato. Dopo il successo, viene giudicato head != null e head.waitStatus!=0 (il waitStatus di head qui è 0, che significa una coda vuota quando si chiama unparkSuccessor per svegliarsi). i nodi successivi e quindi restituiscono true; tryRelease fallisce e restituisce false
booleano protetto tryRelease(int arg)
Prova a modificare lo stato Non esiste un'implementazione specifica in AQS e le sottoclassi devono implementarla da sole. Per questo metodo viene utilizzata la variabile arg del metodo release; il valore restituito indica se lo stato è stato rilasciato con successo
Per una descrizione di arg, vedere la sezione sull'implementazione di seguito.
unparkSuccessor
Modalità di condivisione
Più thread ottengono lo stato di sincronizzazione contemporaneamente
public final void acquireShared(int arg)
La modalità condivisa acquisisce lo stato di sincronizzazione e ignora gli interrupt. Implementazione: chiamare innanzitutto tryAcquireShared per provare a ottenere lo stato di sincronizzazione. Dopo un errore (il risultato è inferiore a 0), chiamare doAcquireShared per ottenere lo stato di sincronizzazione.
protetto int tryAcquireShared(int arg)
Prova a ottenere lo stato di sincronizzazione. Nessun metodo specifico è fornito in AQS ed è implementato da sottoclassi. Un valore restituito inferiore a 0 indica che l'acquisizione non è riuscita; uguale a 0 indica che l'acquisizione ha avuto successo, ma nessun'altra acquisizione in modalità condivisa ha avuto esito positivo;
private void doAcquireShared(int arg)
Dopo essersi unito alla coda, spin tenta di ottenere lo stato di sincronizzazione. La logica di base è sostanzialmente la stessa di acquireQueued in modalità esclusiva. La differenza è che quando il predecessore del nodo è head, il risultato restituito da tryAcquireShared deve essere giudicato è maggiore o uguale a 0 (indica che ci sono ancora risorse, può continuare a propagarsi), quindi chiamare setHeadAndPropagate per impostare gli attributi head e propagazione (impostare una nuova head e giudicare il nodo successore e riattivarlo se appropriato) ; in caso contrario, è comunque necessario chiamare ShouldParkAfterFailedAcquire e parkAndCheckInterrupt per le operazioni successive (parcheggio e successivo giudizio sull'interruzione, ecc.). Inoltre in questo metodo viene eseguito anche il metodo selfTninterrupt per l'impostazione dello stato di interruzione.
provareAcquireShared
Quando il predecessore del nodo è head, provalo prima.
private void setHeadAndPropagate(Node node, int propagate)
L'incarnazione della comunicazione. Ciò che questo metodo controlla effettivamente è il nodo successore del nodo che attualmente acquisisce il lock, ovvero può essere propagato all'indietro una volta. All'inizio, mi chiedevo perché non ho trovato alcun codice simile al risveglio all'indietro in un ciclo. Successivamente ho scoperto che setHeadAndPropagate stesso è in for(;;) Ogni nodo risvegliato eseguirà questo metodo se acquisisce il blocco , quindi questo raggiunge lo scopo della propagazione all'indietro uno per uno. Da notare che non si risvegliano insieme per poi competere, si risvegliano uno per uno.
Continua a riattivare i nodi all'indietro. Questo metodo verrà chiamato solo quando tryAcquireShared restituisce il risultato r>=0 (indicando che ci sono ancora risorse disponibili) e r viene passato al metodo come parametro di propagazione. Questo metodo imposterà il nodo su head e quindi determinerà se può essere passato all'indietro. In tal caso, chiama doReleaseShared per riattivare il nodo successivo.
Logica del giudizio: propagate>0 (che indica che esiste l'autorizzazione) Oppure testa precedente == null O precedente head.waitStatus < 0 Oppure la testa corrente (cioè il nodo) == null Oppure ora head.waitStatus < 0 Quindi vai al passaggio successivo: nodo.successivo == null o node.next.isShared()
In realtà non è molto chiaro: 1. Perché dobbiamo determinare la prevalenza attuale ((h = head) == null) 2. Perché doReleaseShared deve ancora essere eseguito dopo node.next == null
Ipotesi di 1: poiché head può essere modificato da altri thread durante il processo di giudizio, se il nuovo head soddisfa ancora questa condizione, puoi ancora continuare, ritengo che la logica del giudizio qui sia più come se non so perché head sia nullo; ma la provo ancora. Una strategia di elaborazione conservativa, quindi 2 potrebbe essere una strategia conservativa simile
Si ritiene solo che waitStatus<0 sia dovuto al fatto che lo stato della testa può cambiare in SEGNALE e CONDIZIONE non apparirà qui.
vuoto privato doReleaseShared()
Lo spin risveglia il nodo successore, che è diverso dalla modalità indipendente in quanto deve anche garantire le proprietà di propagazione. Implementazione: Ciclo: quando la coda non è vuota, determina il waitStatus di head. Quando è uguale a SIGNAL, CAS imposta waitStatus su 0. In caso di successo, unparkSuccessor(head) verrà chiamato per riattivare il nodo successore , ricomincia dall'inizio; waitStatus è 0 e CAS imposta waitStatus su 0. Quando PROPAGATE fallisce, ricomincia; quindi giudica head==h per vedere se il nodo head è stato modificato e, in tal caso, esci dal ciclo.
Tieni presente che questo metodo non passa parametri e inizia direttamente da head.
Speculazione: il nodo in modalità condivisa può avere solo tre stati: 0 (appena creato), PROPAGATE e SIGNAL.
unparkSuccessor
annullaAcquisisci
public final void acquireSharedInterruptibly(int arg)
La modalità condivisa ottiene lo stato di sincronizzazione e supporta gli interrupt in modo simile alla modalità esclusiva.
provareAcquireShared
private void doAcquireSharedInterruptibly(int arg)
Similmente alla logica doAcquireShared, viene generata un'eccezione dopo che viene rilevata un'interruzione
annullaAcquisisci
public final booleano tryAcquireSharedNanos(int arg, long nanosTimeout)
La modalità condivisa con periodo di timeout ottiene lo stato di sincronizzazione e supporta l'interruzione.
provareAcquireShared
booleano privato doAcquireSharedNanos(int arg, long nanosTimeout)
La logica di valutazione del timeout è simile alla modalità esclusiva e l'altra logica di esecuzione è simile a doAcquireShared
annullaAcquisisci
public final boolean releaseShared(int arg)
La modalità condivisa rilascia lo stato di sincronizzazione
booleano protetto tryReleaseShared(int arg)
Il metodo vuoto, implementato dalle sottoclassi, tenta di rilasciare lo stato di sincronizzazione. arg è il parametro di releaseShared e può essere definito dall'utente; il valore restituito è true quando questo metodo rilascia con successo lo stato di sincronizzazione e altri thread possono acquisirlo, altrimenti restituisce false
doReleaseShared
Qui risvegliamo ancora solo un nodo successivo e la successiva propagazione del risveglio viene effettuata dal nodo risvegliato.
Come implementare la tua serratura basata su AQS
risorse concorrenti
AQS utilizza la variabile privata stato per rappresentare astrattamente le risorse per la competizione di blocco (ad esempio, 1 significa occupato, 0 significa non occupato)
Le operazioni sullo stato richiedono l'uso di metodi pertinenti: getState, setState, compareAndSetState, ecc.
Metodo di implementazione su richiesta
tryAcquire, tryAcquireShared, tryRelease, tryReleaseShared, isHeldExclusively
Il parametro arg di vari metodi in AQS viene infine passato a questi metodi, quindi questi metodi determinano se il parametro arg è richiesto e come utilizzarlo. Questo è il significato di "può rappresentare qualsiasi cosa" nei commenti del codice sorgente.
Blocco esclusivo (modalità indipendente)
booleano protetto tryAcquire(int arg);
L'implementazione delle query e l'ottenimento di risorse competitive viene solitamente completata dall'operazione CAS dello stato. I parametri in entrata possono essere definiti dallo sviluppatore.
Esempio semplice: protetto booleano tryAcquire(int acquisisce) { assert acquisisce == 1; // Definisce che il parametro in entrata può essere solo 1 se (confrontaAndSetState(0, 1)) { restituisce vero; } restituire falso; } protetto booleano tryRelease(int release) { assert releases == 1; // Definisce che il parametro in entrata può essere solo 1 if (getState() == 0) lancia una nuova IllegalMonitorStateException(); setStato(0); restituisce vero; }
booleano protetto tryRelease(int arg);
Per rilasciare risorse concorrenti, solitamente la variabile di stato viene decrementata o azzerata. I parametri in entrata possono essere definiti dallo sviluppatore.
Non dovrebbero esserci blocchi o attese all'interno del metodo
serratura condivisa
protetto int tryAcquireShared(int acquisisce)
I parametri in entrata sono definiti dallo sviluppatore e possono essere considerati come il numero di risorse che si desidera ottenere; il valore restituito int può essere considerato come il numero rimanente di risorse condivise;
Esempio semplice: protetto int tryAcquireShared(int acquisisce) {//non corretto per (;;) { int disponibile = getState(); int rimanente = disponibile - acquisisce; if (rimanente < 0 || compareAndSetState(disponibile, rimanente)) ritorno rimanente; } } protetto booleano tryReleaseShared(int releases) { per (;;) { int corrente = getState(); int next = versioni attuali; if (compareAndSetState(current, next)) restituisce vero; } }
booleano protetto tryReleaseShared(int releases)
I parametri in entrata sono definiti dagli stessi sviluppatori e possono essere considerati come il numero di risorse che desiderano rilasciare.
variabile di condizione
Sebbene la logica di implementazione di AQS non utilizzi lo stato, quando si utilizzano variabili di condizione, lo stato verrà utilizzato come parametro in entrata del rilascio (int arg) per impostazione predefinita, quindi è spesso necessario modificare la variabile di stato durante l'implementazione di questi metodi.
booleano protetto isHeldExclusively()
Restituisce se il thread corrente occupa esclusivamente il blocco mutex corrispondente alla variabile di condizione
Metodi di implementazione comuni: booleano protetto isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } protetto booleano tryAcquire(int acquisisce) { ... setExclusiveOwnerThread(Thread.currentThread()); restituisce vero; }
RientranteReadWriteLock
ReentrantLock
introduzione
Le serrature rientranti si dividono in due metodi: serrature giuste e serrature ingiuste.
implementare l'interfaccia
Serratura
Serializzabile
classe interiore
classe statica astratta Sync
La classe astratta è la base per l'implementazione di ReetrantLock
ereditare
AbstractQueuedSynchronizer
metodo principale
blocco del vuoto astratto()
È un metodo astratto per acquisire un blocco Deve essere implementato da una sottoclasse?
final booleano nonfairTryAcquire(int acquisisce)
Implementazione tryLock ingiusta L'implementazione specifica è: CAS acquisisce il blocco quando nessun thread attualmente lo acquisisce. In caso di successo, il thread esclusivo del blocco viene impostato sul thread corrente; se il thread corrente detiene già il blocco, il numero di blocchi correnti viene aggiornato. In questi due casi verrà restituito true, a indicare che il blocco è mantenuto, mentre negli altri casi verrà restituito false.
Allora perché un'implementazione ingiusta dovrebbe essere inserita nella classe genitore Sync? Poiché questo metodo viene utilizzato per i fair lock e per i lock unfair, i tryLock di entrambi si basano su questa implementazione.
protetto booleano finale tryRelease(int releases)
L'implementazione di tryRelease in AQS consiste nel calcolare innanzitutto il numero totale di blocchi meno il numero di blocchi rilasciati per ottenere il numero di blocchi mantenuti rimanenti. c Se c è 0, cancellare lo stato sul valore di c e restituire se non vengono mantenute serrature.
booleano finale protetto isHeldExclusively()
isHeldExclusively implementato in AQS, restituisce getExclusiveOwnerThread() == Thread.currentThread()
final int getHoldCount()
Restituisce quanti blocchi detiene il thread corrente. Questo metodo si basa sul giudizio di isHeldExclusively
booleano finale isLocked()
return getState() != 0; Indica se un thread mantiene il blocco
discussione finale getOwner()
Restituisce il thread che mantiene il lock, altrimenti null
oggettoCondizione finale nuovaCondizione()
nuovoOggettoCondizione()
private void readObject(java.io.ObjectInputStream s)
Metodo di deserializzazione, imposta lo stato su 0
classe finale statica FairSync
Classe di implementazione del fair lock
ereditare
Sincronizzazione
metodo principale
blocco del vuoto finale()
acquisire(1)
Il parametro 1 qui ha un significato pratico, che è diverso da CountDownLatch.
protetto booleano finale tryAcquire(int acquisisce)
Prova ad acquisire l'implementazione di Fair Lock. Il processo di implementazione è: quando il lock occupato c è 0, se non c'è nessun thread in primo piano e cas imposta con successo c su acquisisce, quindi imposta il thread corrente come thread esclusivo e restituisce true se il thread corrente contiene già il lock, aggiorna il numero di blocchi e restituisce true Restituisce false in tutti gli altri casi
hasQueuedPredecessors
C'è più giudizio qui che blocco ingiusto Anche se nessun thread attualmente acquisisce il blocco, se c'è un thread in attesa per un tempo più lungo, rinuncerà all'acquisizione del blocco.
classe finale statica NonfairSync
Classe di implementazione del blocco ingiusto
ereditare
Sincronizzazione
metodo principale
blocco del vuoto finale()
Per prima cosa prova direct cas(0,1). Se ha successo, significa che nessun altro sta attualmente acquisendo il blocco, quindi imposta il thread esclusivo come thread corrente, se fallisce (1)
In effetti, anche il metodo tryAcquire del blocco ingiusto proverà cas. Il cas diretto qui potrebbe essere quello di evidenziare l'ingiustizia. Le chiamate precedenti potrebbero ottenere risorse prima.
protetto booleano finale tryAcquire(int acquisisce)
return nonfairTryAcquire(acquisisce) L'implementazione ingiusta esiste già in Sync, chiamala direttamente
Variabili membro
sincronizzazione finale privata
Indica se il blocco è implementato in modo corretto o scorretto e corrisponde a FairSync o NonfairSync dopo l'istanziazione.
Costruttore
public ReentrantLock()
Blocco ingiusto costruito per impostazione predefinita
public ReentrantLock(fiera booleana)
Costruisci una serratura giusta o una serratura ingiusta in base alla giusta scelta
metodo principale
blocco pubblico nullo()
sincronizzazione.lock()
public void lockInterruptibly()
sync.acquisireInterrompibilmente(1)
booleano pubblico tryLock()
return sync.nonfairTryAcquire(1); Se il blocco viene acquisito con successo o il thread corrente detiene già il blocco, restituisce true, altrimenti restituisce false
Non importa quale sia il lucchetto qui, è un tentativo ingiusto di acquisirlo.
public booleano tryLock(timeout lungo, unità TimeUnit)
restituisce sync.tryAcquireNanos(1, unit.toNanos(timeout))
tryAcquireNano chiama tryAcquire, quindi c'è una differenza tra equità e ingiustizia.
sblocco pubblico nullo()
sincronizzazione.rilascio(1)
Condizione pubblica newCondition()
restituisce sync.newCondition()
public int getHoldCount()
return sync.getHoldCount() Il numero di blocchi mantenuti dal thread corrente
booleano pubblico isHeldByCurrentThread()
restituisce sync.isHeldExclusively() se il thread corrente mantiene il blocco
booleano pubblico isLocked()
restituisce sync.isLocked()
booleano finale pubblico isFair()
restituisce l'istanza di sincronizzazione di FairSync
Discussione protetta getOwner()
restituisce sync.getOwner()
booleano finale pubblico hasQueuedThreads()
restituisce sync.hasQueuedThreads() se ci sono thread nella coda
public final booleano hasQueuedThread(thread)
return sync.isQueued(thread); Se il thread corrente è in coda
public final int getQueueLength()
restituisce sync.getQueueLength();
Raccolta protetta<Thread> getQueuedThreads()
restituisce sync.getQueuedThreads();
public booleano hasWaiters(Condizione condizione)
Se ci sono thread in attesa della condizione specificata (corrispondente a se ci sono thread nello stato CONDITION nella coda delle condizioni)
public int getWaitQueueLength(Condizione condizione)
Il numero di thread nello stato CONDITION nella coda delle condizioni per una determinata condizione
Collezione protetta<Thread> getWaitingThreads(Condizione condizione)
Condizione
introduzione
Le variabili di condizione dell'interfaccia vengono utilizzate principalmente per gestire la dipendenza dell'esecuzione del thread da determinati stati, mappare i metodi di monitoraggio dell'oggetto (wait/notify/notifyAll) per dirigere gli oggetti e combinarli con l'uso di qualsiasi implementazione di Lock per ottenere ciascuno di essi. l'effetto di più set di attesa. Equivale a Lock che sostituisce la sincronizzazione e Condition che sostituisce il metodo monitor, ovvero Condition deve essere utilizzato insieme a Lock.
Le principali differenze tra i metodi Condition e Monitor sono: 1. La condizione supporta più code di attesa e un'istanza di blocco può essere associata a più condizioni. 2. La condizione può supportare le impostazioni di interruzione della risposta e timeout
Il significato profondo dell'associazione di più condizioni è che i thread possono essere gestiti in base a diverse situazioni. Il cosiddetto controllo "più fine" significa che i thread bloccati a causa di condizioni diverse si trovano in code di condizioni diverse e verranno risvegliati solo quando devono essere risvegliati. Entrare nella coda di sincronizzazione per competere, si differenzia e isola
capire
La differenza rispetto alla competizione di blocco generale è che la condizione enfatizza le condizioni. In un ambiente multi-thread, le condizioni devono essere soddisfatte prima che determinate operazioni possano essere eseguite; mentre la competizione di blocco generale equivale semplicemente a competere per il diritto di eseguire un determinato pezzo di codice .
Modello di programmazione usuale: Ottieni il lucchetto; while (lo stato condizionale non è soddisfatto) { blocco di sblocco; Il thread si blocca e attende finché non viene soddisfatta la condizione per la notifica; Riacquisire la serratura; } Operazioni sulle sezioni critiche; blocco di sblocco;
Relazione ternaria: lock, wait, asserzione condizionale (predicato). Ogni chiamata di attesa è implicitamente associata a una specifica asserzione condizionale; quando si chiama wait per una specifica asserzione condizionale, il chiamante deve già contenere il blocco della coda condizionale relativa all'asserzione e questo blocco deve proteggere le condizioni che costituiscono l'asserzione condizionale. Variabili di stato
predicato: una funzione che restituisce il tipo bool
creare
Creata tramite il metodo newCondition dell'interfaccia Lock, la classe da implementare implementa la propria logica (sostanzialmente creando un'istanza di ConditionObject)
metodo principale
attesa vuota()
Lascia che il thread attenda finché non viene risvegliato da un segnale o da un'interruzione; quando viene chiamato, il blocco associato alla condizione verrà automaticamente rilasciato e il thread interromperà l'esecuzione dell'attività corrente finché non verrà risvegliato da signal/signalAll/interrupt/spurious wakeup. Il thread deve riprovare ad acquisire il blocco associato alla condizione prima di chiamare il metodo wait.
attesa booleana(molto tempo, unità TimeUnit)
Similmente ad waitNanos, puoi specificare l'unità di tempo e restituire false allo scadere del tempo, altrimenti restituire true
lungo waitNanos(lungo nanosTimeout)
Simile ad wait, ma si sveglierà al più tardi dopo l'orario specificato e restituirà il tempo non utilizzato
void attendono ininterrottamente();
Simile ad wait, ma non risponde alle interruzioni
booleano waitUntil(Data di scadenza)
Similmente ad waitNanos, il modo di specificare l'ora è diverso. Se la scadenza è stata raggiunta restituisce false, altrimenti restituisce true.
segnale vuoto()
Riattiva un thread in stato di attesa Dopo che il thread si è riattivato, è necessario acquisire nuovamente il blocco.
segnale vuotoTutto()
Riattiva tutti i thread in stato di attesa
Serratura
introduzione
L'interfaccia fornisce alcuni metodi comuni per richiedere/rilasciare i blocchi
Confronto con sincronizzato
I relativi meccanismi di sincronizzazione (come oggetti di monitoraggio, ecc.) sono integrati e non sono accessibili a livello di lingua. Lock è una soluzione alternativa a livello di lingua, quindi puoi ottenere alcune comodità a livello di lingua (come know se il blocco è stato acquisito con successo, ecc.)
Il processo di blocco e sblocco di sincronizzato viene completato dalla parola chiave stessa, mentre Lock deve chiamare esplicitamente il metodo di blocco e sblocco.
Lock può utilizzare variabili di condizione per utilizzare i blocchi in modo più flessibile
Il blocco può rispondere agli interrupt, ma la sincronizzazione no
metodo principale
blocco vuoto()
void lockInterrompibilmente()
Agli interrupt verrà data risposta durante il processo di richiesta di blocco.
booleano tryLock()
Prova ad acquisire il lock e restituisce direttamente il risultato (senza bloccare il thread corrente)
booleano tryLock(da molto tempo, unità TimeUnit)
tryLock con timeout e interruzione della risposta
sblocco annullato();
Condizione nuovaCondizione()
Restituisce un oggetto Condizione associato al blocco
LockSupport
introduzione
Primitive di base del blocco dei thread (correlate ai permessi) utilizzate per creare blocchi e altre classi di sincronizzazione
Il livello inferiore si basa sull'implementazione non sicura
Metodo di costruzione
Privato, la costruzione di oggetti di questa classe non è consentita
Variabili membro
privato statico finale sun.misc.Unsafe UNSAFE
Costruisci in un blocco di codice statico
sun.misc.Unsafe
privato statico finale lungo parkBlockerOffset
Ottenere la posizione di offset dell'attributo parkBlocker nell'oggetto della classe Thread tramite Unsafe
parkBlocker è un record quando il thread è bloccato, che facilita il monitoraggio e gli strumenti diagnostici per identificare il motivo per cui il thread è bloccato; è nullo quando il thread non è bloccato;
SEED lungo finale statico privato
Ottenere la posizione di offset della proprietà threadLocalRandomSeed nella classe Thread tramite Unsafe
privato statico finale lungo SONDA
Ottenere la posizione di offset della proprietà threadLocalRandomProbe nella classe Thread tramite Unsafe
privato statico finale lungo SECONDARIO
Ottenere la posizione di offset della proprietà threadLocalRandomSecondarySeed nella classe Thread tramite Unsafe
Queste tre proprietà sono impostate da ThreadLocalRandom
metodo principale
Oggetto statico pubblico getBlocker(Thread t)
Ottieni l'oggetto parkBlocker nell'oggetto Thread tramite Unsafe
private static void setBlocker(Thread t, Object arg)
Metodo privato, imposta l'oggetto parkBlocker in Thread tramite Unsafe, i seguenti metodi di impostazione del blocco chiamano questo metodo
parco pubblico statico vuoto()
Blocca il thread e attendi il "permesso" per continuare l'esecuzione.
Implementazione specifica (Hotspot): classe Parker
具体的实现与平台相关,每个线程都有一个Parker实例,在linux实现中实际上是以mutex和condition最终实现了锁和阻塞的过程(具体看笔记文章,这里不写了)
主要成员变量(linux实现)
private volatile int _counter
表示许可,大于0表示已获取许可,每次park只会将其设置为1,不会递增
Pertanto, chiamare due volte "riprendi parcheggio" e poi "parcheggia" due volte bloccherà comunque la seconda volta.
protected pthread_mutex_t _mutex[1]
互斥变量
在代码中可以通过linux原子指令pthread_mutex_lock、pthread_mutex_trylock、pthread_mutex_unlock对变量进行加锁和解锁操作
protected pthread_cond_t _cond[1]
条件变量
利用线程间共享的全局变量进行同步的一种机制,一般和互斥锁结合使用(避免条件变量的竞争);可以通过pthread_cond_wait、pthread_cond_timedwait等指令进行操作
C'è anche una coda di attesa nel kernel Linux per gestire i processi bloccati
方法
park
unpark
Differenze d'uso da wait
wait deve attendere la notifica prima di potersi riattivare. C'è un processo sequenziale e l'unpark che park attende può essere chiamato prima di park. In questo momento, il programma dopo park può continuare l'esecuzione.
wait deve ottenere il monitor dell'oggetto, ma park no
Avviso
Se si verifica un'interruzione nel parco, anche il parco verrà ripristinato ma non genererà un'eccezione di interruzione.
parco pubblico statico vuoto (blocco oggetti)
A causa del ruolo di parkBlocker, è consigliabile utilizzare il metodo park con parametri di blocco. Dopo averlo utilizzato, è possibile visualizzare il messaggio su quale oggetto è bloccato il thread tramite strumenti diagnostici come jstack, ad esempio il parcheggio in attesa. <0x000000045ed12298> (un oggetto xxxx con nome completo)
public static void parkNanos (blocco oggetti, nano lunghi)
parco pubblico statico del vuotoNanos(nano lunghi)
Il tempo di blocco massimo prima che il programma continui l'esecuzione
public static void parkUntil(blocco oggetto, scadenza lunga)
parco pubblico statico vuotoFino al(scadenza lunga)
public static void unpark(Thread thread)