Robert Matejczuk
Gr. 33A
Nr Indeksu: 98045
Laboratorium 5
Celem tych zajęć było:
Opanowanie podstawowych metod synchronizacji w Javie
Wstęp:
Czym są wątki:
Tworząc swoje aplikacje, szczególnie te wykorzystujące interfejs użytkownika w której pewna
czynność, jak na przykład obliczenie wyniku skomplikowanej funkcji, czy pobranie pewnych danych z
bazy danych, zabierało dużo czasu, a przez to aplikacja sprawiała wrażenie jakby się zawiesiła. Nie
jest to pożądana funkcjonalność i każdy programista chciałby jej uniknąć.
Istnieje jednak rozwiązanie, które w stosunkowo prosty sposób pozwala poradzić sobie z tym
problemem są to wątki.
Wątki pozwalają na symultaniczne wykonywanie pewnych operacji dzięki czemu czas wykonania
pewnych operacji można znacząco skrócić. W przypadku przykładu z zawieszeniem się interfejsu
użytkownika można pewne skomplikowane obliczenia wykonać asynchronicznie w tle, dzięki czemu
użytkownik aplikacji będzie miał lepsze odczucia w związku z jej użytkowaniem. Na obrazku wygląda
to tak:
To co istotne to fakt, że zastosowanie wątków sprawdza się nawet na procesorze, który posiada tylko
jeden rdzeń. Jest to spowodowane tym, że każdy z wątków może otrzymywać swój czas procesora na
wykonanie pewnych operacji. W przypadku jednego wątku nie ma wyjścia jeśli wchodzimy do funkcji
obliczającej skomplikowane równanie, cały interfejs użytkownika jest zamrażany aż do momentu
skończenia obliczeń, natomiast jeśli obliczenia uruchomimy w wątku niezależnym od interfejsu będą
one naprzemiennie otrzymywały krótki czas procesora i będą sprawiały wrażenie wykonywania się
równoległego (a w przypadku procesora wielordzeniowego faktycznie tak będą działały) oczywiście
pomijamy tu fakt tego, że obok nich wykonuje się wiele innych procesów, które również walczą o
uzyskanie czasu procesora. Bardzo ważne jest również to, że jeśli uruchomimy jeden po drugim np.
10 wątków, to wcale nie będą one wykonywały się sekwencyjnie jeden po drugim, jeśli sami o to nie
zadbamy. Jeśli jakieś zadanie nie zdąży się wykonać w czasie, który został dla niego przydzielony, to
może ono zostać przerwane na pewien czas.
Kiedy wykorzystywać wątki? W wielu sytuacjach:
Wszelkie obliczenia, które mogą zablokować interfejs użytkownika powinny być wykonywane
asynchronicznie
Animacje, które powinny być przetwarzane niezależnie od interfejsu użytkownika
Pobieranie danych z internetu (zamiast przetwarzać strony internetowe jedna po drugiej
można połączyć się np. z 10 jednocześnie)
W ogólności wszystkie operacje wejścia/wyjścia, zapis i odczyt plików, czy baz danych
Złożone obliczenia, które mogą być podzielone na mniejsze podzadania
I wiele innych
Program 1:
Zawartość pliku Pub.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package pub;
/**
*
* @author Drake
*/
import java.util.logging.Level;
import java.util.logging.Logger;
public class Pub {
int liczba_klientow, liczba_kufli;
int[] tablica_kufli;
/**
* Konstruktor sparametryzowany dla pubu
* @param liczba_klientow Liczba klientow w pubie
* @param liczba_kufli Liczba kufli w pubie
*/
public Pub(int liczba_klientow, int liczba_kufli)
{
this.liczba_kufli = liczba_kufli;
this.liczba_klientow = liczba_klientow;
tablica_kufli = new int[this.liczba_kufli];
//wszystkie kufle sa puste po otwarciu pubu
for(int i=0; i< this.liczba_kufli; i++) {
tablica_kufli[i] = 1;
}
}
/**
* Metoda, w ktorej Klient przekazany jako argument może odebrać jeden z pustych kufli.
* W przypadku braku wolnego kufla klient zawiesza swoje działanie
* i czeka aż ktoś inny odda kufel i obudzi wątek oczekujący
* @param klient Klient, ktory chce zabrac pusty kufel
*/
public synchronized void wezKufel(Klient klient)
{
System.out.println("Klient nr. " + klient.nr_klienta + " czeka na kufel");
while(true)
{
try
{
//sprawdzamy wszystkie dostepne kufle czy sa wolne
for(int indeks = 0; indeks < liczba_kufli; indeks++)
{
//sprawdzamy czy aktualnie sprawdzany kufel jest wolny
if (tablica_kufli[indeks] == 1)
{
tablica_kufli[indeks] = 0;
klient.nr_zabranego_kufla = indeks;
break;
}
}
//sprawdzamy czy klient nie znalazl kufla
if (klient.nr_zabranego_kufla == -1) {
wait(); //oczekujemy az inny klient odda kufel i obudzi watki oczekujace
}
else {
break;
}
} catch (InterruptedException ex) {
Logger.getLogger(Pub.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Wyjatek: wait - wezKufel");
}
}
System.out.println("Klient nr. " + klient.nr_klienta + " zabral kufel nr. " + (klient.nr_zabranego_kufla+1));
}
/**
* Metoda nalewajaca piwo Klientowi, ktory zostal przekazany jako argument
* @param klient Klient, ktory chce nalac sobie piwo do kufla
*/
public synchronized void nalejPiwo(Klient klient)
{
try
{
System.out.println("Klient nr. " + klient.nr_klienta + " nalewa piwo do kufla nr. " +
(klient.nr_zabranego_kufla+1));
Thread.sleep(1000);
System.out.println("Klient nr. " + klient.nr_klienta + " nalal piwo do kufla nr. " +
(klient.nr_zabranego_kufla+1));
}
catch(InterruptedException ie)
{
System.out.println("Wyjatek: nalejPiwo sleep");
}
}
/**
* Metoda, w ktorej Klient przekazany jako argument oddaje pusty kufel
* @param klient Klient, ktory chce oddac pusty kufel
*/
public synchronized void oddajKufel(Klient klient)
{
System.out.println("Klient nr. " + klient.nr_klienta + " odklada kufel nr. " + (klient.nr_zabranego_kufla+1));
tablica_kufli[klient.nr_zabranego_kufla] = 1; //oddajemy kufel
klient.nr_zabranego_kufla = -1; //klient nie posiada aktualnie kufla
notifyAll(); //budzimy wszystkich oczekujacych klientow
}
/* ***********************
*Procedura glowna programu
*********************** */
public static void main(String[] args)
{
int liczba_kufli = 1;
int liczba_klientow = 2;
Pub pub = new Pub(liczba_klientow, liczba_kufli);
Klient[] klienci = new Klient[liczba_klientow];
//tworze watki
for(int i = 0; i < liczba_klientow; i++) {
klienci[i] = new Klient(i+1, pub);
}
//uruchamiam watki
for(int i = 0; i < liczba_klientow; i++) {
klienci[i].start();
}
}
}
/**
* Klasa Klient, ktora pije piwo z kufli w pubie
* @author Mytka
*/
class Klient extends Thread {
/** Pub, w ktorym klient sie znajduje */
Pub pub;
/** ID klienta */
int nr_klienta;
/** Nr. kufla, ktory aktualnie posiada klient. Jesli -1 to nie posiada zadnego */
int nr_zabranego_kufla;
/**
* Konstruktor sparametryzowany
* @param nr ID klienta
* @param pub Pub, w ktorym znajduje sie klient
*/
public Klient(int nr, Pub pub)
{
this.nr_klienta = nr;
this.pub = pub;
nr_zabranego_kufla = -1;
}
@Override
public void run()
{
//klient pije 2 piwa
for(int i=1; i<=2; i++)
{
//klient bierze kufel
pub.wezKufel(this);
//klient nalewa sobie piwo
pub.nalejPiwo(this);
//klient pije piwo
System.out.println("Klient nr. " + nr_klienta + " pije piwo w kuflu nr. " + (nr_zabranego_kufla+1));
//klient oddaje kufel
pub.oddajKufel(this);
}
}
}
Wynik programu:
run:
Klient nr. 1 czeka na kufel
Klient nr. 1 zabral kufel nr. 1
Klient nr. 2 czeka na kufel
Klient nr. 1 nalewa piwo do kufla nr. 1
Klient nr. 1 nalal piwo do kufla nr. 1
Klient nr. 1 pije piwo w kuflu nr. 1
Klient nr. 1 odklada kufel nr. 1
Klient nr. 1 czeka na kufel
Klient nr. 1 zabral kufel nr. 1
Klient nr. 1 nalewa piwo do kufla nr. 1
Klient nr. 1 nalal piwo do kufla nr. 1
Klient nr. 1 pije piwo w kuflu nr. 1
Klient nr. 1 odklada kufel nr. 1
Klient nr. 2 zabral kufel nr. 1
Klient nr. 2 nalewa piwo do kufla nr. 1
Klient nr. 2 nalal piwo do kufla nr. 1
Klient nr. 2 pije piwo w kuflu nr. 1
Klient nr. 2 odklada kufel nr. 1
Klient nr. 2 czeka na kufel
Klient nr. 2 zabral kufel nr. 1
Klient nr. 2 nalewa piwo do kufla nr. 1
Klient nr. 2 nalal piwo do kufla nr. 1
Klient nr. 2 pije piwo w kuflu nr. 1
Klient nr. 2 odklada kufel nr. 1
BUILD SUCCESSFUL (total time: 4 seconds)
Program 2:
Zawartość pliku Pub_trylock.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package pub_trylock;
/**
*
* @author Drake
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Pub_trylock {
int liczba_klientow, liczba_kufli;
int[] tablica_kufli;
Lock kran = new ReentrantLock();
Lock lada = new ReentrantLock();
/**
* Konstruktor sparametryzowany dla pubu
* @param liczba_klientow Liczba klientow w pubie
* @param liczba_kufli Liczba kufli w pubie
*/
public Pub_trylock(int liczba_klientow, int liczba_kufli)
{
this.liczba_kufli = liczba_kufli;
this.liczba_klientow = liczba_klientow;
tablica_kufli = new int[this.liczba_kufli];
//wszystkie kufle sa puste po otwarciu pubu
for(int i=0; i< this.liczba_kufli; i++) {
tablica_kufli[i] = 1;
}
}
/**
* Metoda, w ktorej Klient przekazany jako argument może odebrać jeden z pustych kufli.
* W przypadku braku wolnego kufla klient zawiesza swoje działanie i czeka
* aż ktoś inny odda kufel i obudzi wątek oczekujący
* @param klient Klient, ktory chce zabrac pusty kufel
*/
public void wezKufel(Klient klient)
{
System.out.println("Klient nr. " + klient.nr_klienta + " czeka na kufel");
while(true)
{
//sprawdzamy czy mozemy dostac sie do lady, zeby odebrac kufel
if(lada.tryLock())
{
try
{
//sprawdzamy wszystkie kufle
for(int indeks = 0; indeks < liczba_kufli; indeks++)
{
if (tablica_kufli[indeks] == 1)
{
tablica_kufli[indeks] = 0;
klient.nr_zabranego_kufla = indeks;
break;
}
}
if (klient.nr_zabranego_kufla != -1) {
break;
}
}
finally
{
//po odebraniu kufla zwalniamy lade dla innych klientow
lada.unlock();
}
}
}
System.out.println("Klient nr. " + klient.nr_klienta + " zabral kufel nr. " + (klient.nr_zabranego_kufla+1));
}
/**
* Metoda nalewajaca piwo Klientowi, ktory zostal przekazany jako argument
* @param klient Klient, ktory chce nalac sobie piwo do kufla
*/
public void nalejPiwo(Klient klient)
{
while(true)
{
//sprawdzamy czy mamy dostep do kranu, zeby nalac do kufla piwo
if(kran.tryLock())
{
try
{
System.out.println("Klient nr. " + klient.nr_klienta + " nalewa piwo do kufla nr. " +
(klient.nr_zabranego_kufla+1));
Thread.sleep(1000);
System.out.println("Klient nr. " + klient.nr_klienta + " nalal piwo do kufla nr. " +
(klient.nr_zabranego_kufla+1));
break;
}
catch(InterruptedException ie)
{
System.out.println("Wyjatek: nalejPiwo sleep");
}
finally
{
//po nalaniu zwalniamy kran
kran.unlock();
}
}
}
}
/**
* Metoda, w ktorej Klient przekazany jako argument oddaje pusty kufel
* @param klient Klient, ktory chce oddac pusty kufel
*/
public void oddajKufel(Klient klient)
{
System.out.println("Klient nr. " + klient.nr_klienta + " odklada kufel nr. " + (klient.nr_zabranego_kufla+1));
tablica_kufli[klient.nr_zabranego_kufla] = 1;
klient.nr_zabranego_kufla = -1;
}
/* ***********************
*Procedura glowna programu
*********************** */
public static void main(String[] args)
{
int liczba_kufli = 1;
int liczba_klientow = 2;
Pub_trylock pub = new Pub_trylock(liczba_klientow, liczba_kufli);
Klient[] klienci = new Klient[liczba_klientow];
//tworze watki
for(int i = 0; i < liczba_klientow; i++) {
klienci[i] = new Klient(i+1, pub);
}
//uruchamiam watki
for(int i = 0; i < liczba_klientow; i++) {
klienci[i].start();
}
}
}
/**
* Klasa Klient, ktora pije piwo z kufli w pubie
* @author Mytka
*/
class Klient extends Thread {
/** Pub, w ktorym klient sie znajduje */
Pub_trylock pub;
/** ID klienta */
int nr_klienta;
/** Nr. kufla, ktory aktualnie posiada klient. Jesli -1 to nie posiada zadnego */
int nr_zabranego_kufla;
/**
* Konstruktor sparametryzowany
* @param nr ID klienta
* @param pub Pub, w ktorym znajduje sie klient
*/
public Klient(int nr, Pub_trylock pub)
{
this.nr_klienta = nr;
this.pub = pub;
nr_zabranego_kufla = -1;
}
@Override
public void run()
{
//klient pije 2 piwa
for(int i=1; i<=2; i++)
{
//klient bierze kufel
pub.wezKufel(this);
//klient nalewa sobie piwo
pub.nalejPiwo(this);
//klient pije piwo
System.out.println("Klient nr. " + nr_klienta + " pije piwo w kuflu nr. " + (nr_zabranego_kufla+1));
//klient oddaje kufel
pub.oddajKufel(this);
}
}
}
Wynik Programu:
run:
Klient nr. 2 czeka na kufel
Klient nr. 1 czeka na kufel
Klient nr. 2 zabral kufel nr. 1
Klient nr. 2 nalewa piwo do kufla nr. 1
Klient nr. 2 nalal piwo do kufla nr. 1
Klient nr. 2 pije piwo w kuflu nr. 1
Klient nr. 2 odklada kufel nr. 1
Klient nr. 2 czeka na kufel
Klient nr. 1 zabral kufel nr. 1
Klient nr. 1 nalewa piwo do kufla nr. 1
Klient nr. 1 nalal piwo do kufla nr. 1
Klient nr. 1 pije piwo w kuflu nr. 1
Klient nr. 1 odklada kufel nr. 1
Klient nr. 2 zabral kufel nr. 1
Klient nr. 2 nalewa piwo do kufla nr. 1
Klient nr. 1 czeka na kufel
Klient nr. 2 nalal piwo do kufla nr. 1
Klient nr. 2 pije piwo w kuflu nr. 1
Klient nr. 2 odklada kufel nr. 1
Klient nr. 1 zabral kufel nr. 1
Klient nr. 1 nalewa piwo do kufla nr. 1
Klient nr. 1 nalal piwo do kufla nr. 1
Klient nr. 1 pije piwo w kuflu nr. 1
Klient nr. 1 odklada kufel nr. 1
BUILD SUCCESSFUL (total time: 4 seconds)
Wnioski:
Wątki w Javie można tworzyć na kilka sposobów, poprzez:
jawne rozszerzenie klasy Thread (Program 1 i 2)
stworzenie klasy implementującej interfejs Runnable, który może być wykonany w osobnym
wątku (Thread)
stworzenie klasy implementującej interfejs Callable, który może być wykonany w osobnym
wątku (Thread)
Preferowane jest stosowanie rozszerzeń interfejsów (czyli 2 i 3 punkt), ponieważ dają one dużo lepszą
elastyczność, szczególnie jeśli dojdziemy do momentu szeregowania wątków, utrzymywania stałej puli
wątków wykonujących się w tle. Interfejsy Runnable i Callable są do siebie bardzo podobne, jednak
najważniejszą różnicą jest to, że Callable może zwrócić w wyniku pewną wartość, natomiast w
przypadku Runnable nie ma takiej możliwości.
W moich programach zastosowałem pierwszą możliwość czyli jawne rozszerzenie klasy Thread.
Wątki uruchamiam za pomocą metody start() która powoduje rozpoczęcie wykonania kodu, który
stworzyliśmy w metodzie run().
Wydruki programów udowadniają, że klienci nie będą pić piw po kolei w takiej kolejności jak przyszli
do pubu. Czyli pierwszy klient który będzie pić piwo może ale nie musi być pierwszym klientem który
przybył do baru. Może się okazać, że przy warunku opuszczenia baru po wypiciu dwóch piw wyjdzie
jako ostatni. Dzieje się tak za sprawą losowego przypisywania czasu procesora różnym wątkom
(klientom).
W drugim programie zastosowałem metodę TryLock.
TryLock zalety:
są bardziej efektywne od synchronized w sytuacji dużej konkurencji wątków o zasoby,
jako "zwykłe" obiekty Javy mogą być dostępne przez referencje w wielu miejscach kodu (np.
przekazywane jako argumenty konstruktorów czy metod),
mogą być zamykane i zwalniane w różnych strukturalnie sekcjach kodu np. w różnych
metodach (ale wykonywanych przez ten sam wątek), synchronized może być użyte tylko w
ramach tego samego bloku,
możliwe jest sprawdzenie, czy rygiel jest zamknięty (inny wątek wykonuje sekcję krytyczną) i
np. wtedy zajęcie się innymi czynnościami, a nie blokowanie bieżącego wątku na ryglu, mamy
tu możliwości zastosowania metody tryLock() lub bezpośrednie odpytywanie obiektu-rygla czy
jest zamknięty,
możliwe jest oczekiwanie na uzyskanie dostępu do sekcji krytycznej (otwarcie rygla przez inny
wątek) przez określony czas (nie chcemy blokować bieżącego wątku zbyt długo), służy do
tego metoda tryLock(...) z podanym czasem oczekiwania,
możliwe jest przerwanie wątku zablokowanego na jakimś ryglu, tu używamy metody
lockInterruptibly() (synchronized nie daje tej możliwości).
Podstawowy schemat działania.
Lock lock = new ReentrantLock(); // utworzenie rygla
// ....
// KOD WYKONYWANY WSPÓABIEŻNIE
lock.lock(); // zamknięcie rygla (1)
// ... kod sekcji krytycznej
lock.unlock(); // zwolnienie rygla (2)
Wyszukiwarka
Podobne podstrony:
Lab5Lab5 1 R4 lab51238 OT PW Geopozpwlab5Wplyw reklamy na proces PW 0Heinlein, Robert A Successful Operationwięcej podobnych podstron