Systemy Rozproszone - Ćwiczenie 3
1 Współbieżny dostęp do obiektu
Poniżej znajduje się bardzo prosta klasa - licznik, mający 2 metody: inc() zwiększający licznik o 1 i getValue() zwracający aktualną wartość licznika.
W ramach rozgrzewki napisz program, który będzie zwiększać licznik przez 2 sekundy, a po 2 sekundach wypisze wartość licznika. Do tego celu można np.
wykorzytać System.currentTimeMillis().
/∗ p l i k S i m p l e C o u n t e r . j a v a ∗/
public c l a s s S i m p l e C o u n t e r { private i n t v a l u e ;
public S i m p l e C o u n t e r ( ) { v a l u e = 0 ;
}
public void i n c ( ) { v a l u e ++;
}
public i n t g e t V a l u e ( ) { return v a l u e ;
} }
Następnie stworzymy program, który utworzy 3 wątki i 1 obiekt Simple- Counter. W konstruktorze każdemu wątkowi przekazana zostanje referencja do obiektu SimpleCounter. Każdy proces zwiększa licznik 10 razy. Zastanów się co ostatecznie wypisze program na wyjściu, a następnie sprawdź to uruchomiając program.
public c l a s s SyncDemo extends Thread { S i m p l e C o u n t e r s o ;
public SyncDemo ( S i m p l e C o u n t e r o b j ) { s o = o b j ;
}
public void run ( ) {
f o r ( i n t i =0; i <10; i ++) { s o . i n c ( ) ;
} }
public s t a t i c void main ( S t r i n g a r g s [ ] ) {
S i m p l e C o u n t e r c o u n t e r = new S i m p l e C o u n t e r ( ) ; SyncDemo t h r e a d 1 = new SyncDemo ( c o u n t e r ) ; SyncDemo t h r e a d 2 = new SyncDemo ( c o u n t e r ) ; SyncDemo t h r e a d 3 = new SyncDemo ( c o u n t e r ) ; t h r e a d 1 . s t a r t ( ) ;
t h r e a d 2 . s t a r t ( ) ; t h r e a d 3 . s t a r t ( ) ; try {
t h r e a d 1 . j o i n ( ) ; t h r e a d 2 . j o i n ( ) ; t h r e a d 3 . j o i n ( ) ; }
catch ( I n t e r r u p t e d E x c e p t i o n e ) { System . o u t . p r i n t l n ( " I n t e r r u p t e d " ) ; }
System . ou t . p r i n t l n ( c o u n t e r . g e t V a l u e ( ) ) ; }
}
Zwiększ ilość iteracji pętli w metodzie run() do 100, 1000, 100000. Przetestuj działanie programu dla różnej liczby wątków (od 1 do 10). Czy wyniki są zgodne z oczekiwaniami? Co mogło pójść nie tak? Zastanów się nad tym wspólnie z kolegą siedzącym obok Ciebie. Podpowiedź znajduje się na następnej stronie.
Co poszło nie tak?
Wszystkie 3 procesy wykonywały równolegle metodę inc(). Linia value++ to tak naprawdę 3 instrukcje atomowe:
• skopiowanie zmiennej do rejestru: LD R,value
• zwiększ rejestr: INC R
• skopiowanie rejestru do zmiennej: LD value,R
Zastanów się jaki może wystąpić przeplot tych operacji atomowych podczas wy- konywania metody inc() przez 2 procesy. Odpowiedź znajduje się na następnej stronie.
Niekorzystny przeplot operacji
/∗
∗ p r z y k l a d na z w i e k s z a n i e o 1 p r z e z 2 p r o c e s y ( P0 , P1 ) r o w n o c z e s n i e
∗
P0 : LD R, x z a l a d u j x do r e j e s t r u
P0 : INC R z w i e k s z r e j e s t r
P1 : LD R, x P1 : INC R
P0 : ST R, x z a p i s z x do r e j e s t r u
P1 : ST R, x
∗
∗/
Problem równoczesnego dostępu do metody można rozwiązać poprzez użycie słowa synchronized w deklaracji metody. Poczytaj nt. synchronized i spróbuj naprawić licznik.
2 Sekcje krytyczne wewnątrz metody
Przykład:
public c l a s s AdvancedCounter { private i n t v a l u e 1 = 0 ; private i n t v a l u e 2 = 0 ;
private O b j e c t l o c k 1 = new O b j e c t ( ) ; private O b j e c t l o c k 2 = new O b j e c t ( ) ;
public void i n c 1 ( ) { synchronized ( l o c k 1 ) {
v a l u e 1 ++;
} }
public void i n c 2 ( ) { synchronized ( l o c k 2 ) {
v a l u e 2 ++;
} }
public i n t g e t 1 ( ) { return v a l u e 1 ; }
public i n t g e t 2 ( ) { return v a l u e 2 ;
} }
3 Sekcje krytyczne - piaskownica
Poeksperymentuj z poniższym programem używając słowa kluczowego synchronized zarówno dla metod jak i bloków kodu.
c l a s s Shared {
public void f o o ( S t r i n g threadName , long t i m e ) { System . ou t . p r i n t l n ( threadName +
" ␣ i s ␣ r u n n i n g ␣ f o o ( ) ␣ f o r ␣ "+t i m e+" ␣ms , ␣ c t : " + System . c u r r e n t T i m e M i l l i s ( ) ) ;
long t = System . c u r r e n t T i m e M i l l i s ( ) ;
while ( System . c u r r e n t T i m e M i l l i s () − t < t i m e ) { Math . a t a n ( System . c u r r e n t T i m e M i l l i s ( ) ) ; }
}
public void bar ( S t r i n g threadName , long t i m e ) { System . ou t . p r i n t l n ( threadName +
" ␣ i s ␣ r u n n i n g ␣ bar ( ) ␣ f o r ␣ "+t i m e+" ␣ms , ␣ c t : " + System . c u r r e n t T i m e M i l l i s ( ) ) ;
long t = System . c u r r e n t T i m e M i l l i s ( ) ;
while ( System . c u r r e n t T i m e M i l l i s () − t < t i m e ) { Math . a t a n ( System . c u r r e n t T i m e M i l l i s ( ) ) ; }
} }
c l a s s ThreadA extends Thread { Shared s h a r e d ;
ThreadA ( Shared i n s t a n c e ) { t h i s . s h a r e d = i n s t a n c e ; }
public void run ( ) {
s h a r e d . f o o ( "A" , 5 0 0 0 ) ; s h a r e d . ba r ( "A" , 1 0 0 0 ) ;
System . ou t . p r i n t l n ( "A␣ i s ␣ done " ) ; }
}
c l a s s ThreadB extends Thread { Shared s h a r e d ;
ThreadB ( Shared i n s t a n c e ) { t h i s . s h a r e d = i n s t a n c e ; }
public void run ( ) {
s h a r e d . f o o ( "B" , 1 0 0 0 ) ; s h a r e d . ba r ( "B" , 2 0 0 0 ) ;
System . ou t . p r i n t l n ( "B␣ i s ␣ done " ) ; }
}
public c l a s s J a v a A p p l i c a t i o n 1 { /∗ ∗
∗ @param a r g s t h e command l i n e arguments
∗/
public s t a t i c void main ( S t r i n g [ ] a r g s ) { // TODO c o d e a p p l i c a t i o n l o g i c h e r e Shared s h a r e d = new Shared ( ) ;
ThreadA t h 1 = new ThreadA ( s h a r e d ) ; ThreadB t h2 = new ThreadB ( s h a r e d ) ; long t = System . c u r r e n t T i m e M i l l i s ( ) ; t h 1 . s t a r t ( ) ;
t h 2 . s t a r t ( ) ; try {
t h 1 . j o i n ( ) ; t h 2 . j o i n ( ) ; }
catch ( I n t e r r u p t e d E x c e p t i o n e ) { System . o u t . p r i n t l n ( e ) ;
}
System . ou t . p r i n t l n ( " done ␣ i n ␣ " + ( System . c u r r e n t T i m e M i l l i s ( ) − t ) + " ␣ms" ) ; }
}
4 Zakleszczenie
Dlaczego dochodzi do zakleszczenia?
public c l a s s DeadlockDemo1 { s t a t i c c l a s s F r i e n d {
private f i n a l S t r i n g name ;
public F r i e n d ( S t r i n g name ) { t h i s . name = name ;
}
public S t r i n g getName ( ) { return t h i s . name ;
}
public synchronized void bow ( F r i e n d bower ) { System . o u t . f o r m a t ( "%s : ␣%s ␣ has ␣bowed␣ t o ␣me!%n" ,
t h i s . name , bower . getName ( ) ) ; bower . bowBack ( t h i s ) ;
}
public synchronized void bowBack ( F r i e n d bower ) { System . o u t . f o r m a t ( "%s : ␣%s ␣ has ␣bowed␣ back ␣ t o ␣me!%n" ,
t h i s . name , bower . getName ( ) ) ; }
}
public s t a t i c void main ( S t r i n g [ ] a r g s ) {
f i n a l F r i e n d a l p h o n s e = new F r i e n d ( " Alphonse " ) ; f i n a l F r i e n d g a s t o n = new F r i e n d ( " Gaston " ) ; new Thread (new Runnable ( ) {
public void run ( ) { a l p h o n s e . bow ( g a s t o n ) ; }
} ) . s t a r t ( ) ;
new Thread (new Runnable ( ) { public void run ( ) {
g a s t o n . bow ( a l p h o n s e ) ; }
} ) . s t a r t ( ) ; }
}
5 Zadania
Na razie nic....