Java >> Java tutorial >  >> Tag >> java.util

Guide til java.util.concurrent.Locks

1. Oversigt

Enkelt sagt er en lås en mere fleksibel og sofistikeret trådsynkroniseringsmekanisme end standard synkroniseret blokere.

Låsen interface har eksisteret siden Java 1.5. Det er defineret i java.util.concurrent.lock pakke, og det giver omfattende operationer til låsning.

I denne øvelse vil vi udforske forskellige implementeringer af Låsen interface og deres applikationer.

2. Forskelle mellem lås og synkroniseret blok

Der er nogle få forskelle mellem brugen af ​​synkroniseret blok og ved hjælp af Lås API'er:

  • En synkroniseret bloker er fuldt ud indeholdt i en metode. Vi kan have Lås API'er lock() og unlock() drift i separate metoder.
  • En ssynkroniseret blok understøtter ikke retfærdigheden. Enhver tråd kan erhverve låsen, når den er frigivet, og ingen præference kan angives. Vi kan opnå retfærdighed inden for Låsen API'er ved at angive retfærdigheden ejendom. Den sørger for, at den længst ventende tråd får adgang til låsen.
  • En tråd bliver blokeret, hvis den ikke kan få adgang til den synkroniserede blok . Låsen API giver tryLock() metode. Tråden får kun lås, hvis den er tilgængelig og ikke holdes af nogen anden tråd. Dette reducerer blokeringstiden for tråden, der venter på låsen.
  • En tråd, der er i "venter"-tilstand for at få adgang til synkroniseret blok kan ikke afbrydes. Låsen API giver en metode lockInterruptibly() der kan bruges til at afbryde tråden, når den venter på låsen.

3. Lås API

Lad os tage et kig på metoderne i Låsen grænseflade:

  • void lock() – Anskaf låsen, hvis den er tilgængelig. Hvis låsen ikke er tilgængelig, bliver en tråd blokeret, indtil låsen udløses.
  • void lockInterruptibly() – Dette ligner lock() , men det tillader den blokerede tråd at blive afbrudt og genoptage eksekveringen gennem en kastet java.lang.InterruptedException .
  • boolean tryLock() – Dette er en ikke-blokerende version af lock() metode. Den forsøger at erhverve låsen med det samme, returner true, hvis låsning lykkes.
  • boolsk tryLock (lang timeout, TimeUnit timeUnit) – Dette ligner tryLock() , bortset fra at den afventer den givne timeout, før den opgiver at forsøge at erhverve Låsen .
  • ugyldig unlock() låser Låsen op forekomst.

En låst forekomst skal altid låses op for at undgå deadlock-tilstand.

En anbefalet kodeblok til at bruge låsen bør indeholde en try/catch og endelig blokere:

Lock lock = ...; 
lock.lock();
try {
    // access to the shared resource
} finally {
    lock.unlock();
}

Ud over Låsen interface, har vi en ReadWriteLock grænseflade, der opretholder et par låse, en til skrivebeskyttede operationer og en til skriveoperation. Læselåsen kan holdes samtidigt af flere tråde, så længe der ikke er nogen skrivning.

ReadWriteLock erklærer metoder til at erhverve læse- eller skrivelåse:

  • Lås readLock() returnerer den lås, der bruges til at læse.
  • Lås writeLock() returnerer låsen, der bruges til at skrive.

4. Lås implementeringer

4.1. ReentrantLock

ReentrantLock klasse implementerer Låsen interface. Den tilbyder den samme samtidighed og hukommelsessemantik som den implicitte skærmlås, der tilgås ved hjælp af synkroniseret metoder og udsagn, med udvidede muligheder.

Lad os se, hvordan vi kan bruge ReentrantLock til synkronisering:

public class SharedObject {
    //...
    ReentrantLock lock = new ReentrantLock();
    int counter = 0;

    public void perform() {
        lock.lock();
        try {
            // Critical section here
            count++;
        } finally {
            lock.unlock();
        }
    }
    //...
}

Vi skal sikre os, at vi ombryder lock() og unlock() kalder i Prøv endelig blokere for at undgå dødvande situationer.

Lad os se, hvordan tryLock() virker:

public void performTryLock(){
    //...
    boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);
    
    if(isLockAcquired) {
        try {
            //Critical section here
        } finally {
            lock.unlock();
        }
    }
    //...
}

I dette tilfælde kalder tråden tryLock() vil vente i et sekund og vil opgive at vente, hvis låsen ikke er tilgængelig.

4.2. ReentrantReadWriteLock

ReentrantReadWriteLock klasse implementerer ReadWriteLock grænseflade.

Lad os se reglerne for anskaffelse af ReadLock eller WriteLock af en tråd:

  • Læselås – Hvis ingen tråd har erhvervet skrivelåsen eller anmodet om den, kan flere tråde erhverve læselåsen.
  • Skrivelås – Hvis ingen tråde læser eller skriver, kan kun én tråd opnå skrivelåsen.

Lad os se på, hvordan du gør brug af ReadWriteLock :

public class SynchronizedHashMapWithReadWriteLock {

    Map<String,String> syncHashMap = new HashMap<>();
    ReadWriteLock lock = new ReentrantReadWriteLock();
    // ...
    Lock writeLock = lock.writeLock();

    public void put(String key, String value) {
        try {
            writeLock.lock();
            syncHashMap.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
    ...
    public String remove(String key){
        try {
            writeLock.lock();
            return syncHashMap.remove(key);
        } finally {
            writeLock.unlock();
        }
    }
    //...
}

For begge skrivemetoder skal vi omgive den kritiske sektion med skrivelåsen - kun én tråd kan få adgang til den:

Lock readLock = lock.readLock();
//...
public String get(String key){
    try {
        readLock.lock();
        return syncHashMap.get(key);
    } finally {
        readLock.unlock();
    }
}

public boolean containsKey(String key) {
    try {
        readLock.lock();
        return syncHashMap.containsKey(key);
    } finally {
        readLock.unlock();
    }
}

For begge læsemetoder skal vi omgive den kritiske sektion med læselåsen. Flere tråde kan få adgang til denne sektion, hvis ingen skrivehandling er i gang.

4.3. StampedLock

StampedLock er introduceret i Java 8. Den understøtter også både læse- og skrivelåse.

Låserhvervelsesmetoder returnerer dog et stempel, der bruges til at frigøre en lås eller til at kontrollere, om låsen stadig er gyldig:

public class StampedLockDemo {
    Map<String,String> map = new HashMap<>();
    private StampedLock lock = new StampedLock();

    public void put(String key, String value){
        long stamp = lock.writeLock();
        try {
            map.put(key, value);
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    public String get(String key) throws InterruptedException {
        long stamp = lock.readLock();
        try {
            return map.get(key);
        } finally {
            lock.unlockRead(stamp);
        }
    }
}

En anden funktion leveret af StampedLock er optimistisk låsning. Det meste af tiden behøver læseoperationer ikke at vente på, at skriveoperationen er fuldført, og som et resultat af dette er den fuldgyldige læselås ikke påkrævet.

I stedet kan vi opgradere til læselås:

public String readWithOptimisticLock(String key) {
    long stamp = lock.tryOptimisticRead();
    String value = map.get(key);

    if(!lock.validate(stamp)) {
        stamp = lock.readLock();
        try {
            return map.get(key);
        } finally {
            lock.unlock(stamp);               
        }
    }
    return value;
}

5. Arbejder med Condition s

Betingelsen klasse giver en tråd mulighed for at vente på, at en tilstand opstår, mens den kritiske sektion udføres.

Dette kan forekomme, når en tråd får adgang til den kritiske sektion, men ikke har den nødvendige betingelse for at udføre sin handling. For eksempel kan en læsetråd få adgang til låsen af ​​en delt kø, der stadig ikke har nogen data at forbruge.

Traditionelt giver Java wait() , notify() og notifyAll() metoder til trådforbindelse.

Tilstand s har lignende mekanismer, men vi kan også specificere flere betingelser:

public class ReentrantLockWithCondition {

    Stack<String> stack = new Stack<>();
    int CAPACITY = 5;

    ReentrantLock lock = new ReentrantLock();
    Condition stackEmptyCondition = lock.newCondition();
    Condition stackFullCondition = lock.newCondition();

    public void pushToStack(String item){
        try {
            lock.lock();
            while(stack.size() == CAPACITY) {
                stackFullCondition.await();
            }
            stack.push(item);
            stackEmptyCondition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public String popFromStack() {
        try {
            lock.lock();
            while(stack.size() == 0) {
                stackEmptyCondition.await();
            }
            return stack.pop();
        } finally {
            stackFullCondition.signalAll();
            lock.unlock();
        }
    }
}

6. Konklusion

I denne artikel så vi forskellige implementeringer af Låsen interface og den nyligt introducerede StampedLock klasse.

Vi undersøgte også, hvordan vi kan gøre brug af Betingelsen klasse til at arbejde med flere forhold.

Den komplette kode til denne artikel er tilgængelig på GitHub.


Java tag