Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Oppiskele Lukko ja Ehto | Korkean Tason Synkronointimekanismit
Monisäikeisyys Javassa

bookLukko ja Ehto

Javassa vakiomuotoinen synkronointi perustuu synchronized-avainsanaan ja sisäänrakennettuihin monitoriolioihin. Joissakin tapauksissa synchronized ei kuitenkaan riitä, erityisesti kun tarvitaan joustavampaa säikeiden hallintaa.

Yleiskuvaus

Lock-rajapinta ja Condition-rajapinta, jotka esiteltiin java.util.concurrent.locks -paketissa, tarjoavat kehittyneitä säikeiden hallinnan ominaisuuksia.

Tässä kuvassa näet, että ensimmäinen säie ottaa lukon käyttöön lock()-metodilla, ja tällä hetkellä toinen säie ei voi ottaa samaa lukkoa. Heti kun kaikki lukon sisällä oleva koodi on suoritettu, kutsutaan unlock()-metodia ja lukko vapautetaan. Vasta tämän jälkeen toinen säie voi ottaa lukon.

Ero

Ero näiden kahden rajapinnan välillä on, että Lock-toteutukset ovat korkean tason vaihtoehto synchronized-lohkolle, ja Condition-rajapinnan toteutukset ovat vaihtoehto notify()/wait()-metodeille. Molemmat rajapinnat kuuluvat java.util.concurrent.locks -pakettiin.

Käytännön esimerkkejä

Kuvittele, että hallinnoit jonotusjärjestelmää tapahtuman rekisteröintiä varten. Estääksesi ylivuodon ja varmistaaksesi asianmukaisen paikkajaon, sinun täytyy käyttää estomekanismeja ja ehtoja pitämään uusien rekisteröintien virta odottamassa, kunnes vapaa paikka tulee saataville.

ReentrantLock-luokka

ReentrantLock -luokka java.util.concurrent.locks -paketista on Lock -rajapinnan toteutus. Se tarjoaa toiminnallisuudet lukkojen eksplisiittiseen hallintaan.

ReentrantLock-luokan päämenetelmät:

  • lock(): Ottaa lukon haltuun;
  • unlock(): Vapauttaa lukon;
  • tryLock(): Yrittää ottaa lukon haltuun ja palauttaa true, jos onnistuu;
  • tryLock(long timeout, TimeUnit unit): Yrittää ottaa lukon haltuun määrätyn ajan;
  • newCondition(): Luo ehdon nykyiselle Lock:lle.
Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }

Kuten huomaat, increment()-metodissa käytetään lukitusta Lock-olion avulla. Kun säie siirtyy metodiin, se ottaa lukon käyttöön komennolla lock.lock(), suorittaa koodin ja vapauttaa lukon finally-lohkossa komennolla lock.unlock(), jotta muut säikeet voivat päästä sisään.

Lukko vapautetaan finally-lohkossa syystä: tämä lohko suoritetaan lähes aina, myös poikkeustilanteissa, paitsi jos ohjelma lopetetaan.

Condition-rajapinta

Condition voidaan luoda vain sitomalla se tiettyyn Lock-toteutukseen. Tästä syystä Condition-metodit vaikuttavat vain kyseisen Lock-toteutuksen lukitukseen.

Main.java

Main.java

copy
12
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();

Condition-rajapinnan pääasialliset metodit:

  • await(): Odottaa signaalia toiselta säikeeltä;
  • signal(): Vapauttaa yhden säikeen, joka odottaa ehtoa;
  • signalAll(): Vapauttaa kaikki säikeet, jotka odottavat ehtoa.
Main.java

Main.java

copy
12345678910111213141516
private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }

waitForCondition() -metodi estää säiettä jatkamasta, kunnes ready-muuttuja on true, mikä ilmaisee, että ehto on täyttynyt. Kun ehto täyttyy, säie jatkaa suoritustaan ja näyttää viestin “Condition met!”

Note
Huomio

Kun await()-metodia kutsutaan, säie keskeytetään ja se myös vapauttaa lukon, jonka se on ottanut haltuunsa. Kun säie herää, sen tulee ottaa lukko uudelleen haltuun ja vasta sitten se alkaa suorittaa!

Koodiesimerkki

Tarkastellaan nyt esimerkkiä, jossa käytetään ReentrantLock- ja Condition-luokkia tapahtumaan rekisteröitymisen hallintaan:

Lyhyt videoleike

Lukitus ReentrantLockilla: register()-metodi ottaa lukon käyttöön lock.lock()-kutsulla estääkseen useita säikeitä suorittamasta koodia samanaikaisesti.

Ehto Condition-oliolla: Jos vapaita paikkoja ei ole, säie kutsuu spaceAvailable.await() odottaakseen, kunnes tila vapautuu.

Lukituksen vapautus: Kun säie on vapauttanut tilan cancel()-metodilla, se kutsuu spaceAvailable.signalAll() ilmoittaakseen kaikille odottaville säikeille.

Poikkeusten käsittely: try-finally-lohkojen käyttö varmistaa, että lukko vapautetaan, vaikka poikkeus tapahtuisi.

Note
Huomio

Lock- ja Condition-rakenteiden käyttö Javassa mahdollistaa joustavamman säikeiden ja synkronoinnin hallinnan kuin perinteinen synchronized-mekanismi. Tämä on erityisen hyödyllistä monimutkaisissa tilanteissa, joissa tarvitaan tarkempaa hallintaa säikeiden ja odotusehtojen suhteen.

Oliko kaikki selvää?

Miten voimme parantaa sitä?

Kiitos palautteestasi!

Osio 3. Luku 1

Kysy tekoälyä

expand

Kysy tekoälyä

ChatGPT

Kysy mitä tahansa tai kokeile jotakin ehdotetuista kysymyksistä aloittaaksesi keskustelumme

Awesome!

Completion rate improved to 3.33

bookLukko ja Ehto

Pyyhkäise näyttääksesi valikon

Javassa vakiomuotoinen synkronointi perustuu synchronized-avainsanaan ja sisäänrakennettuihin monitoriolioihin. Joissakin tapauksissa synchronized ei kuitenkaan riitä, erityisesti kun tarvitaan joustavampaa säikeiden hallintaa.

Yleiskuvaus

Lock-rajapinta ja Condition-rajapinta, jotka esiteltiin java.util.concurrent.locks -paketissa, tarjoavat kehittyneitä säikeiden hallinnan ominaisuuksia.

Tässä kuvassa näet, että ensimmäinen säie ottaa lukon käyttöön lock()-metodilla, ja tällä hetkellä toinen säie ei voi ottaa samaa lukkoa. Heti kun kaikki lukon sisällä oleva koodi on suoritettu, kutsutaan unlock()-metodia ja lukko vapautetaan. Vasta tämän jälkeen toinen säie voi ottaa lukon.

Ero

Ero näiden kahden rajapinnan välillä on, että Lock-toteutukset ovat korkean tason vaihtoehto synchronized-lohkolle, ja Condition-rajapinnan toteutukset ovat vaihtoehto notify()/wait()-metodeille. Molemmat rajapinnat kuuluvat java.util.concurrent.locks -pakettiin.

Käytännön esimerkkejä

Kuvittele, että hallinnoit jonotusjärjestelmää tapahtuman rekisteröintiä varten. Estääksesi ylivuodon ja varmistaaksesi asianmukaisen paikkajaon, sinun täytyy käyttää estomekanismeja ja ehtoja pitämään uusien rekisteröintien virta odottamassa, kunnes vapaa paikka tulee saataville.

ReentrantLock-luokka

ReentrantLock -luokka java.util.concurrent.locks -paketista on Lock -rajapinnan toteutus. Se tarjoaa toiminnallisuudet lukkojen eksplisiittiseen hallintaan.

ReentrantLock-luokan päämenetelmät:

  • lock(): Ottaa lukon haltuun;
  • unlock(): Vapauttaa lukon;
  • tryLock(): Yrittää ottaa lukon haltuun ja palauttaa true, jos onnistuu;
  • tryLock(long timeout, TimeUnit unit): Yrittää ottaa lukon haltuun määrätyn ajan;
  • newCondition(): Luo ehdon nykyiselle Lock:lle.
Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }

Kuten huomaat, increment()-metodissa käytetään lukitusta Lock-olion avulla. Kun säie siirtyy metodiin, se ottaa lukon käyttöön komennolla lock.lock(), suorittaa koodin ja vapauttaa lukon finally-lohkossa komennolla lock.unlock(), jotta muut säikeet voivat päästä sisään.

Lukko vapautetaan finally-lohkossa syystä: tämä lohko suoritetaan lähes aina, myös poikkeustilanteissa, paitsi jos ohjelma lopetetaan.

Condition-rajapinta

Condition voidaan luoda vain sitomalla se tiettyyn Lock-toteutukseen. Tästä syystä Condition-metodit vaikuttavat vain kyseisen Lock-toteutuksen lukitukseen.

Main.java

Main.java

copy
12
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();

Condition-rajapinnan pääasialliset metodit:

  • await(): Odottaa signaalia toiselta säikeeltä;
  • signal(): Vapauttaa yhden säikeen, joka odottaa ehtoa;
  • signalAll(): Vapauttaa kaikki säikeet, jotka odottavat ehtoa.
Main.java

Main.java

copy
12345678910111213141516
private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }

waitForCondition() -metodi estää säiettä jatkamasta, kunnes ready-muuttuja on true, mikä ilmaisee, että ehto on täyttynyt. Kun ehto täyttyy, säie jatkaa suoritustaan ja näyttää viestin “Condition met!”

Note
Huomio

Kun await()-metodia kutsutaan, säie keskeytetään ja se myös vapauttaa lukon, jonka se on ottanut haltuunsa. Kun säie herää, sen tulee ottaa lukko uudelleen haltuun ja vasta sitten se alkaa suorittaa!

Koodiesimerkki

Tarkastellaan nyt esimerkkiä, jossa käytetään ReentrantLock- ja Condition-luokkia tapahtumaan rekisteröitymisen hallintaan:

Lyhyt videoleike

Lukitus ReentrantLockilla: register()-metodi ottaa lukon käyttöön lock.lock()-kutsulla estääkseen useita säikeitä suorittamasta koodia samanaikaisesti.

Ehto Condition-oliolla: Jos vapaita paikkoja ei ole, säie kutsuu spaceAvailable.await() odottaakseen, kunnes tila vapautuu.

Lukituksen vapautus: Kun säie on vapauttanut tilan cancel()-metodilla, se kutsuu spaceAvailable.signalAll() ilmoittaakseen kaikille odottaville säikeille.

Poikkeusten käsittely: try-finally-lohkojen käyttö varmistaa, että lukko vapautetaan, vaikka poikkeus tapahtuisi.

Note
Huomio

Lock- ja Condition-rakenteiden käyttö Javassa mahdollistaa joustavamman säikeiden ja synkronoinnin hallinnan kuin perinteinen synchronized-mekanismi. Tämä on erityisen hyödyllistä monimutkaisissa tilanteissa, joissa tarvitaan tarkempaa hallintaa säikeiden ja odotusehtojen suhteen.

Oliko kaikki selvää?

Miten voimme parantaa sitä?

Kiitos palautteestasi!

Osio 3. Luku 1
some-alt