16. Multithreading (Java Applications) Χειμερινό Εξάμηνο 2012 Πέτρος Κωμοδρόμος komodromos@ucy.ac.cy http://www.eng.ucy.ac.cy/petros 1
Θέματα Εισαγωγή: Τι είναι ένα thread; Δημιουργία και εκτέλεση Threads Καταστάσεις (States) ενός Thread: Ο κύκλος ζωής ενός Thread Προτεραιότητες (Priorities) Χρονοπρογραμματισμός (Scheduling) των Threads Συγχρονισμός (Synchronization) Threads Multithreading με GUI Thread Pools Άλλες χρήσιμες τάξεις και Interfaces του java.util.concurrent Monitors και Monitor Locks 2
Εισαγωγή Ένα thread (διεργασία/νήμα) έχει, όπως και ένα πρόγραμμα, μία αρχή από όπου ξεκινά, μια σειρά εντολών που εκτελεί, και ένα τέλος αφού ολοκληρώσει το σκοπό (task) του. Ένα thread όμως δεν είναι το ίδιο πρόγραμμα, αλλά τρέχει στα πλαίσια ενός προγράμματος. Με το multithreading (πολυνημάτωση) παρέχονται σε μία εφαρμογή πολλαπλά threads of execution επιτρέποντας την παράλληλη εκτέλεση κάποιων ξεχωριστών διεργασιών, σαν τμήματα, με διαφορετικούς σκοπούς το καθένα, μιας μεγαλύτερης διεργασίας (προγράμματος). 3
Single-threading: Μια διενέργεια κάποιας εφαρμογής με single-thread τρόπο εκτελέσεως πρέπει να ολοκληρωθεί προτού ξεκινήσουν άλλες διενέργειες. Υπάρχει μία συγκεκριμένη ροή εκτέλεσης με διαδοχική εκτέλεση εντολών και σε κάθε χρονική στιγμή υπάρχει ένα μοναδικό σημείο μέχρι το οποίο έχουν εκτελεστεί οι εντολές και το οποίο είναι το επόμενο που θα εκτελεστεί. Multi-threading: Αντιθέτως, εφαρμογές με multithreading επιτρέπουν σε πολλαπλές διενέργειες ή εργασίες να εκτελούνται ταυτοχρόνως, είτε κατανέμοντας τις υπολογιστικές απαιτήσεις σε συστήματα με πολλαπλούς επεξεργαστές, αν είναι διαθέσιμοι, είτε προσομοιώνοντας ταυτόχρονη εκτέλεση (concurrency) πολλαπλών ενεργειών σε συστήματα με ένα μόνο επεξεργαστή, βασιζόμενες στους πόρους που έχουν δοθεί στο πρόγραμμα (process). 4
Ένα thread αν και έχει τη δική του αρχή, ροή ελέγχου (flow of control) και τέλος, όμως δεν είναι πρόγραμμα και συνεπώς δεν μπορεί να τρέξει από μόνο του. Τρέχει στα πλαίσια ενός προγράμματος χρησιμοποιώντας τους πόρους που παρέχονται στο πρόγραμμα. Η Java έχει ενσωματωμένες δυνατότητες για multithreading σαν μέρος της γλώσσας εξασφαλίζοντας portability σε διαφορετικά συστήματα Σε κάποιες περιπτώσεις απαιτείται από τον προγραμματιστή τα επιμέρους threads (διεργασίες/νήματα) να είναι σωστά synchronized για να δουλεύουν ορθά 5
APIs σχετικά μεthreads java.util.timer: Για την εκτέλεση μετά από κάποια συγκεκριμένη καθυστέρηση ή επαναληπτικά ενός συγκεκριμένου πολύ απλού σκοπού (task), ο οποίος δίνεται μέσα στη μέθοδο run(), σε ένα thread παρασκηνίου (background thread) (- Using the Timer and TimerTask Classes -) javax.swing.timer: Για προγράμματα με γραφικό περιβάλλον διασύνδεσης (GUI), java.lang.thread: Περιέχει βασικές πληροφορίες για πιο ολοκληρωμένη υποστήριξη multithreading και threads με το thread API και τη γενική συμπεριφορά των threads, όπως το ξεκίνημα (starting), την νάρκωση (sleeping), την εκτέλεση (running), την παραχώρηση (yielding), και την προτεραιότητα του κάθε thread 6
Παράδειγμα επαναληπτικής χρήσης της τάξης Timer import java.util.timer; import java.util.timertask; import java.awt.toolkit; ThreadsEx1.java public class ThreadsEx1 Timer timer; Toolkit toolkit; int repetition, totalrepetitions; int delay; public ThreadsEx1(int delay, int interval, int reps) timer = new Timer(); repetition = 0; this.delay = delay; totalrepetitions = reps; toolkit = Toolkit.getDefaultToolkit(); timer.schedule(new MyTimerSubClass(), 1000*delay, 1000*interval); 7
class MyTimerSubClass extends TimerTask public void run() repetition++; System.out.printf("\n Repetition %d", repetition); System.out.printf(" after %d miliseconds = \n", delay*1000); toolkit.beep(); if(repetition==totalrepetitions) timer.cancel(); public static void main(string args[]) new ThreadsEx1(1,2,5); // (delay, interval, repetitions) System.out.println("\n A ThreadsEx1 object was created: "); 8
9
Χρήσης της Timer σε συγκεκριμένη χρονική στιγμή import java.util.timer; import java.util.timertask; import java.util.calendar; import java.util.date; import java.awt.toolkit; import javax.swing.joptionpane; ThreadsEx2.java public class ThreadsEx2 Timer timer; public ThreadsEx2(Calendar calendar) Date time = calendar.gettime(); timer = new Timer(); timer.schedule(new MyTimerSubClass(), time); 10
class MyTimerSubClass extends TimerTask public void run() JOptionPane.showMessageDialog(null, "Alert", "Reminder!!!!!", JOptionPane.ERROR_MESSAGE); timer.cancel(); public static void main(string args[]) Calendar calendar = Calendar.getInstance(); calendar.add(calendar.second, 30); new ThreadsEx2(calendar); System.out.println("\n A ThreadsEx2 object was created: "); 11
12
Δημιουργία Threads Παρέχοντας τη μέθοδο run(): 1. Επεκτείνοντας την τάξη (class) Thread o Επαναορίζοντας τη μέθοδο run(), η οποία ορίζει τις εντολές που πρέπει να εκτελεστούν για να επιτελέσει το thread το σκοπό (task) του 2. Υλοποιώντας τη διασύνδεση (interface) Runnable o Ορίζοντας τη μέθοδο run(), η οποία ορίζει τις εντολές που πρέπει να εκτελεστούν για να επιτελέσει το thread το σκοπό (task) του 13
Επέκταση της τάξης Thread class MyThreadClass extends Thread ThreadExample1.java MyThreadClass(String name) super(name); public void run() for(int i=0; i<5;i++) System.out.println("Thread: " + getname()); try Thread.sleep((int) (Math.random()*1000)); 14
catch(interruptedexception e) System.out.println("Catch: " + getname()); class ThreadExample1 public static void main(string args[]) MyThreadClass th1 = new MyThreadClass("Thread-1"); MyThreadClass th2 = new MyThreadClass("Thread-2"); MyThreadClass th3 = new MyThreadClass("Thread-3"); th3.start(); th1.start(); th2.start(); 15
16
Υλοποίηση της Runnable διασύνδεσης class MyRunnableClass implements Runnable public void run() ThreadExample2.java for(int i=0; i<5;i++) System.out.println("Thread: " + Thread.currentThread().getName()); try Thread.sleep((int) (Math.random()*1000)); catch(interruptedexception e) System.out.println("InterruptedException in " + Thread.currentThread().getName() + " thread."); 17
class ThreadExample2 public static void main(string args[]) MyRunnableClass r1 = new MyRunnableClass(); MyRunnableClass r2 = new MyRunnableClass(); MyRunnableClass r3 = new MyRunnableClass(); Thread th1 = new Thread(r1,"Thread-1"); Thread th2 = new Thread(r2,"Thread-2"); Thread th3 = new Thread(r3,"Thread-3"); th3.start(); th1.start(); th2.start(); System.out.println("Main: Current Thread: " + Thread.currentThread().getName()); 18
19
Διαχείριση των Threads Αφού δημιουργήσουμε ένα thread με ένα από τους δύο τρόπους πρέπει να μπορούμε να το ξεκινήσουμε και να το σταματήσουμε, σε σχέση και με τα άλλα threads, να συγχρονίσουμε (synchronize) τη χρήση κοινών πόρων, ώστε να μην προκληθεί κάποια ζημιά (resource corruption), αλλά και συντονίσουμε τη δυνατότητα του κάθε thread να τρέξει για να εκτελέσει το σκοπό του, παρέχοντας του χρονομερίδιο του επεξεργαστή (processor) ώστε να μην αποκλείεται. 20
Καταστάσεις (States) ενός Thread : Ο κύκλος ζωής ενός Thread New state Ένα νέο thread ξεκινά τη ζωή του σε ένα νέο (new) state χωρίς όμως ακόμη να του έχουν παρασχεθεί οποιοιδήποτε πόροι από το σύστημα Παραμένει σε αυτή την κατάσταση (state) μέχρι το πρόγραμμα να ξεκινήσει, χρησιμοποιώντας τη μέθοδο start(), το thread, βάζοντας το σε runnable κατάσταση (state) Runnable state Ένα thread φτάνει σε αυτή την κατάσταση (state) όταν κληθεί η start() μέθοδος του, ώστε να του έχουν παρασχεθούν οι απαραίτητοι πόροι από το σύστημα για να εκτελέσει τον σκοπό (task) του και να ενημερωθεί ο thread scheduler. Ο thread scheduler στη συνέχεια καλεί τη μέθοδο run(), η οποία περιέχει τις εντολές που απαιτούνται για να εκτελέσει το thread το σκοπό (task) του. o Όμως εφόσον συνήθως είναι μόνο ένας επεξεργαστής διαθέσιμος ένα thread μπορεί να περιμένει τη σειρά του για να τρέξει πραγματικά τις εντολές όπως καθορίζονται στη μέθοδο run() 21
Not Runnable state Ένα thread μπαίνει σε αυτή την κατάσταση είτε όταν καλείται η μέθοδος sleep() ή η μέθοδος wait() Waiting state o o Ένα thread μεταφέρεται σε αυτή την κατάσταση (state) για να αναμένει ένα άλλο thread να εκτελέσει τον σκοπό (task) του Ένα thread σε αυτή την κατάσταση επιστρέφει στην runnable κατάσταση όταν ειδοποιηθεί από το άλλο thread ότι έχει εκτελέσει τον σκοπό του Timed waiting state o o Ένα thread εισέρχεται σε αυτό το state για να περιμένει για ένα άλλο thread ή για κάποιο συνολικό χρόνο να περάσει Ένα thread σε αυτή την κατάσταση επιστρέφει στην runnable κατάσταση όταν ειδοποιηθεί από κάποιο άλλο thread ή όταν ο χρόνος αναμονής παρέλθει 22
Terminated state Ένα thread εισέρχεται σε αυτή την κατάσταση όταν ολοκληρώσει το σκοπό (task) του Ένα thread δεν πρέπει να χρησιμοποιεί τη μέθοδο stop() για να σταματήσει, αλλά πρέπει, αφού ολοκληρώσει το σκοπό (task) του, να τελειώσει κανονικά τη μέθοδο run() και να βγει από αυτή αφήνοντας το thread να τερματίσει φυσικά τη ζωή και εκτέλεση του Μην χρησιμοποιείτε ποτέ τις μεθόδους stop(), suspend() και resume() 23
Διάγραμμα κυρίων καταστάσεων (States) ενός Thread 24
Μέθοδος getstate() της τάξης Thread Η μέθοδος getstate() επιστρέφει ένα αντικείμενο Thread.State, το οποίο περιγράφει σε ποια κατάσταση (state) βρίσκεται το thread: NEW o Όταν ένα thread δεν έχει ακόμη ξεκινήσει RUNNABLE o Όταν ένα thread έχει ξεκινήσει να εκτελείται BLOCKED o WAITING o Όταν ένα thread είναι μπλοκαρισμένο περιμένοντας για ένα monitor lock Όταν ένα thread περιμένει επ αόριστο για ένα άλλο thread να εκτελέσει κάποια συγκεκριμένη ενέργεια TIMED_WAITING o Όταν ένα thread περιμένει ένα άλλο thread να εκτελέσει κάποια συγκεκριμένη ενέργεια μέχρι όμως κάποιο καθορισμένο χρονικό όριο TERMINATED o Όταν ένα thread έχει τελειώσει, ολοκληρώνοντας ότι είχε να κάνει 25
Thread life-cycle UML state diagram. 26
Runnable κατάσταση σύμφωνα με το λειτουργικό σύστημα ready state Ένα thread σε αυτή την κατάσταση (state) είναι έτοιμο να τρέξει χωρίς να περιμένει κάτι άλλο εκτός από το λειτουργικό σύστημα (operating system) να του παραχωρήσει ένα επεξεργαστή (processor) running state Ένα thread σε αυτή την κατάσταση (state) έχει ένα επεξεργαστή (processor) και εκτελείται, συνήθως, για ένα σύντομο διάστημα του χρόνου του επεξεργαστή το ονομαζόμενο χρονομερίδιο (time slice) ή κβάντο (quantum) προτού επιστρέψει στην κατάσταση ready 27
Προτεραιότητες (Priorities) των Threads Το κάθε thread στη Java έχει μία συγκεκριμένη προτεραιότητα Οι προτεραιότητες των threads στη Java κυμαίνονται μεταξύ MIN_PRIORITY (σταθερά, 1) και MAX_PRIORITY (σταθερά, 10) Threads με μεγαλύτερη προτεραιότητα θεωρούνται πιο σημαντικά και τους παρέχεται επεξεργαστής πριν από άλλα threads με χαμηλότερη προτεραιότητα Η προεπιλεγμένη προτεραιότητα ενός thread είναι η NORM_PRIORITY (σταθερά 5) ή η προτεραιότητα που έχει το thread που το δημιούργησε Χρησιμοποιώντας τη μέθοδο setpriority() μπορεί να τροποποιηθεί η προτεραιότητα ενός thread 28
Χρονοπρογραμματισμός - Thread Scheduling Καθορίζει ποιο thread θα τρέξει στη συνέχεια. Αν το σύστημα υποστηρίζει χρονομερίδια (time-slices) threads με την ίδια προτεραιότητα εξυπηρετούνται (τους δίνετε πρόσβαση στο CPU για να εκτελεστούν) εκ περιτροπής (round-robin fashion) Ένα thread που τρέχει μπορούν να προεκτοπιστεί (preempted) από άλλα threads που έχουν ψηλότερη προτεραιότητα ή από το σύστημα αν υποστηρίζει χρονομερίδια για να δώσει τη δυνατότητα σε κάποιο thread με την ίδια προτεραιότητα να τρέξει Σε κάποιες περιπτώσεις, threads με ψηλότερη προτεραιότητα μπορεί να αναβάλλουν επ αόριστο την εκτέλεση threads με χαμηλότερη προτεραιότητα, το οποίο είναι γνωστό σαν starvation (λιμός) 29
Ο χρονοπρογραμματισμός εξαρτάται από το λειτουργικό σύστημα και τον Η/Υ και συνεπώς πάντα πρέπει να ελέγχονται όλα τα συστήματα στα οποία μπορεί να εκτελεστεί το applet ή application μας. Συστήματα που δεν υποστηρίζουν χωρισμό του διαθέσιμου χρόνου του CPU σε χρονομερίδια (time-slicing) επιλέγουν ένα thread και το εκτελούν αφήνοντας το να τρέχει μέχρι να τελειώσει ή να προεκτοπιστεί από κάποιο άλλο thread με ψηλότερη προτεραιότητα. Ένα πρόγραμμα δεν πρέπει να βασίζεται στη δυνατότητα του συστήματος να χρησιμοποιεί χρονομερίδια (time-slicing) Τα threads σε ένα πρόγραμμα πρέπει εθελοντικά να δίνουν τη δυνατότητα σε άλλα threads να τρέξουν. Μπορεί να χρησιμοποιηθεί η μέθοδος yield() για να δοθεί η δυνατότητα σε άλλα runnable threads με την ίδια προτεραιότητα να τρέξουν. 30
Παράδειγμα χρονοπρογραμματισμού (Thread Scheduling) βάσει προτεραιοτήτων των threads 31
1 ον Παράδειγμα με προτεραιότητες ThreadExample3.java class MyRunnableClass implements Runnable public void run() for(int i=0; i<5;i++) System.out.println("Thread: " + Thread.currentThread().getName()); 32
class ThreadExample3 public static void main(string args[]) MyRunnableClass r1 = new MyRunnableClass(); MyRunnableClass r2 = new MyRunnableClass(); MyRunnableClass r3 = new MyRunnableClass(); Thread th1 = new Thread(r1,"Thread-1"); Thread th2 = new Thread(r2,"Thread-2"); Thread th3 = new Thread(r3,"Thread-3"); th2.setpriority(thread.max_priority); th1.setpriority(thread.norm_priority); th3.setpriority(thread.min_priority); th3.start(); th1.start(); th2.start(); 33
Εκτέλεση σε Η/Υ με ένα processor (CPU) 34
2 ον Παράδειγμα με προτεραιότητες ThreadExample4.java class MyRunnableClass implements Runnable public void run() for(int i=0; i<10;i++) System.out.println("Thread: " + Thread.currentThread().getName()); 35
class ThreadExample4 public static void main(string args[]) MyRunnableClass r1 = new MyRunnableClass(); MyRunnableClass r2 = new MyRunnableClass(); MyRunnableClass r3 = new MyRunnableClass(); Thread th1 = new Thread(r1,"Thread-1"); Thread th2 = new Thread(r2,"Thread-2"); Thread th3 = new Thread(r3,"Thread-3"); th2.setpriority(thread.norm_priority); th1.setpriority(thread.norm_priority); th3.setpriority(thread.min_priority); th3.start(); th1.start(); th2.start(); 36
Εκτέλεση σε Η/Υ με ένα processor (CPU) 37
3 ον Παράδειγμα με προτεραιότητες ThreadExample5.java class MyRunnableClass implements Runnable public void run() for(int i=0; i<10;i++) System.out.println("Thread: " + Thread.currentThread().getName()); Thread.currentThread().yield()); 38
class ThreadExample5 public static void main(string args[]) MyRunnableClass r1 = new MyRunnableClass(); MyRunnableClass r2 = new MyRunnableClass(); MyRunnableClass r3 = new MyRunnableClass(); Thread th1 = new Thread(r1,"Thread-1"); Thread th2 = new Thread(r2,"Thread-2"); Thread th3 = new Thread(r3,"Thread-3"); th2.setpriority(thread.norm_priority); th1.setpriority(thread.norm_priority); th3.setpriority(thread.min_priority); th3.start(); th1.start(); th2.start(); 39
Εκτέλεση σε Η/Υ με ένα processor (CPU) 40
Java SE 5.0: Δημιουργία και εκτέλεση Threads με συνδυασμό διασυνδέσεων (interfaces) Runnable και Executor Runnable interface Ορισμός της μεθόδου run() Εκτελείται από ένα αντικείμενο που υλοποιεί το Executor interface Executor interface Μπορεί εναλλακτικά να χρησιμοποιηθεί αντί της δημιουργίας threads Ορίζει τη μέθοδο execute(runnable command) η οποία εκτελεί το Runnable που δίνεται σαν παράμετρος Δημιουργεί και διαχειρίζεται μία ομάδα threads γνωστή ως thread pool 41
Tάξεις και διασυνδέσεις στο J2SE 5.0 - Thread Pools ExecutorService interface Επεκτείνει τη διασύνδεση Executor ορίζοντας άλλες μεθόδους για τη διαχείριση της ζωής ενός Executor Μπορεί να δημιουργηθεί χρησιμοποιώντας στατικές (static) μεθόδους της τάξης Executors, όπως οι newfixedthreadpool() και newcachedthreadpool() Η μέθοδος shutdown() τερματίζει threads που ολοκληρώνουν τους στόχους τους Μια υλοποίηση του ExecutorService μπορεί να χρησιμοποιηθεί για τη διαχείριση thread pools Executors class Η μέθοδος newfixedthreadpool() δημιουργεί ένα pool αποτελούμενο από ένα σταθερό αριθμό threads το οποίο έχει αναφορά ένα ExecutorService Η μέθοδος newcachedthreadpool() δημιουργεί ένα pool το οποίο δημιουργεί με τη σειρά του νέα threads όποτε χρειάζονται με αναφορά ένα ExecutorService 42
Παράδειγμα με ExecutorService και Executors import java.util.concurrent.executors; import java.util.concurrent.executorservice; class MyRunnableClass implements Runnable public void run() ThreadExample6.java for(int i=0; i<10;i++) System.out.println("Thread: " + Thread.currentThread().getName()); Thread.currentThread().yield(); 43
class ThreadExample6 public static void main(string args[]) MyRunnableClass r1 = new MyRunnableClass(); MyRunnableClass r2 = new MyRunnableClass(); MyRunnableClass r3 = new MyRunnableClass(); ExecutorService executorservice = Executors.newFixedThreadPool(3); executorservice.execute(r1); executorservice.execute(r2); executorservice.execute(r3); executorservice.shutdown(); 44
Εκτέλεση σε Η/Υ με ένα processor (CPU) 45
1 // Fig. 23.4: PrintTask.java 2 // PrintTask class sleeps for a random time from 0 to 5 seconds 3 import java.util.random; 4 5 class PrintTask implements Runnable 6 7 private int sleeptime; // random sleep time for thread 8 private String threadname; // name of thread 9 private static Random generator = new Random(); 10 11 // assign name to thread 12 public PrintTask( String name ) 13 14 threadname = name; // set name of thread 15 16 // pick random sleep time between 0 and 5 seconds 17 sleeptime = generator.nextint( 5000 ); 18 // end PrintTask constructor 19 Implement runnable to create separate thread 46
20 // method run is the code to be executed by new thread 21 public void run() 22 Declare run method to fulfill interface 23 try // put thread to sleep for sleeptime amount of time 24 25 System.out.printf( "%s going to sleep for %d milliseconds.\n", 26 threadname, sleeptime ); 27 28 Thread.sleep( sleeptime ); // put thread to sleep 29 // end try 30 // if thread interrupted while sleeping, print stack trace 31 catch ( InterruptedException exception ) 32 33 exception.printstacktrace(); 34 // end catch 35 36 // print thread name 37 System.out.printf( "%s done sleeping\n", threadname ); 38 // end method run 39 // end class PrintTask 47
1 // Fig. 23.5: RunnableTester.java 2 // Multiple threads printing at different intervals. 3 import java.util.concurrent.executors; 4 import java.util.concurrent.executorservice; 5 6 public class RunnableTester 7 8 public static void main( String[] args ) 9 10 // create and name each runnable 11 PrintTask task1 = new PrintTask( "thread1" ); 12 PrintTask task2 = new PrintTask( "thread2" ); 13 PrintTask task3 = new PrintTask( "thread3" ); 14 15 System.out.println( "Starting threads" ); 16 17 // create ExecutorService to manage threads 18 ExecutorService threadexecutor = Executors.newFixedThreadPool( 3 ); 19 20 // start threads and place in runnable state 21 threadexecutor.execute( task1 ); // start task1 22 threadexecutor.execute( task2 ); // start task2 23 threadexecutor.execute( task3 ); // start task3 24 25 threadexecutor.shutdown(); // shutdown worker threads 26 Create three PrintTasks; each will run in a separate thread Create fixed thread pool to execute and manage threads Execute each task; this method will assign a thread to the runnable Shutdown thread pool when runnables complete their tasks 48
27 System.out.println( "Threads started, main ends\n" ); 28 // end main 29 // end class RunnableTester Starting threads Threads started, main ends thread1 going to sleep for 1217 milliseconds thread2 going to sleep for 3989 milliseconds thread3 going to sleep for 662 milliseconds thread3 done sleeping thread1 done sleeping thread2 done sleeping Starting threads thread1 going to sleep for 314 milliseconds thread2 going to sleep for 1990 milliseconds Threads started, main ends thread3 going to sleep for 3016 milliseconds thread1 done sleeping thread2 done sleeping thread3 done sleeping 49
Συγχρονισμός (Synchronization) των Threads Παρέχεται στον προγραμματιστή για την αποφυγή ταυτόχρονης τροποποίησης κάποιων δεδομένων από δύο ή περισσότερα threads με απροσδιόριστα αποτελέσματα Εξασφαλίζει αποκλειστική πρόσβαση σε ένα κοινό (shared) αντικείμενο, ώστε να μην μπορεί να αλλοιωθεί λανθασμένα (corrupted) Υλοποιείται στη Java χρησιμοποιώντας με συγχρονισμένο κώδικα και την monitor κλειδαριά που κληρονομείται από το Object ή από το J2SΕ 5.0 με κλειδαριές (locks) που ορίζονται άμεσα σε σχέση με κάποιες προϋποθέσεις 50
Παράδειγμα: Παραγωγού/Καταναλωτή (Producer/Consumer) χωρίς συγχρονισμό (synchronization) 1 // Fig. 23.6: Buffer.java 2 // Buffer interface specifies methods called by Producer and Consumer. 3 4 public interface Buffer 5 6 public void set( int value ); // place int value into Buffer 7 public int get(); // return int value from Buffer 8 // end interface Buffer 51
1 // Fig. 23.9: UnsynchronizedBuffer.java 2 // UnsynchronizedBuffer represents a single shared integer. 3 4 public class UnsynchronizedBuffer implements Buffer 5 6 private int buffer = -1; // shared by producer and consumer threads 7 8 // place value into buffer 9 public void set( int value ) 10 11 System.out.printf( "Producer writes\t%2d", value ); 12 buffer = value; 13 // end method set 14 15 // return value from buffer 16 public int get() 17 18 System.out.printf( "Consumer reads\t%2d", buffer ); 19 return buffer; 20 // end method get 21 // end class UnsynchronizedBuffer Set the value of the buffer Read the value of the buffer Shared variable to store data 52
1 // Fig. 23.7: Producer.java 2 // Producer's run method stores the values 1 to 10 in buffer. 3 import java.util.random; 4 5 public class Producer implements Runnable 6 7 private static Random generator = new Random(); 8 private Buffer sharedlocation; // reference to shared object 9 10 // constructor 11 public Producer( Buffer shared ) 12 13 sharedlocation = shared; 14 // end Producer constructor 15 16 // store values from 1 to 10 in sharedlocation 17 public void run() 18 19 int sum = 0; 20 Implement the runnable interface so that producer will run in a separate thread Declare run method to satisfy interface 53
21 for ( int count = 1; count <= 10; count++ ) 22 23 try // sleep 0 to 3 seconds, then place value in Buffer 24 25 Thread.sleep( generator.nextint( 3000 ) ); // sleep thread 26 sharedlocation.set( count ); // set value in buffer 27 sum += count; // increment sum of values 28 System.out.printf( "\t%2d\n", sum ); 29 // end try 30 // if sleeping thread interrupted, print stack trace 31 catch ( InterruptedException exception ) 32 33 exception.printstacktrace(); 34 // end catch 35 // end for 36 37 System.out.printf( "\n%s\n%s\n", "Producer done producing.", 38 "Terminating Producer." ); 39 // end method run 40 // end class Producer Sleep for up to 3 seconds 54
1 // Fig. 23.8: Consumer.java 2 // Consumer's run method loops ten times reading a value from buffer. 3 import java.util.random; 4 5 public class Consumer implements Runnable 6 7 private static Random generator = new Random(); 8 private Buffer sharedlocation; // reference to shared object 9 10 // constructor 11 public Consumer( Buffer shared ) 12 13 sharedlocation = shared; 14 // end Consumer constructor 15 16 // read sharedlocation's value four times and sum the values 17 public void run() 18 19 int sum = 0; 20 Implement the runnable interface so that producer will run in a separate thread Declare run method to satisfy interface 55
21 for ( int count = 1; count <= 10; count++ ) 22 23 // sleep 0 to 3 seconds, read value from buffer and add to sum 24 try 25 26 Thread.sleep( generator.nextint( 3000 ) ); 27 sum += sharedlocation.get(); 28 System.out.printf( "\t\t\t%2d\n", sum ); 29 // end try 30 // if sleeping thread interrupted, print stack trace 31 catch ( InterruptedException exception ) 32 33 exception.printstacktrace(); 34 // end catch 35 // end for 36 37 System.out.printf( "\n%s %d.\n%s\n", 38 "Consumer read values totaling", sum, "Terminating Consumer." ); 39 // end method run 40 // end class Consumer Sleep for up to 3 seconds 56
1 // Fig 23.10: SharedBufferTest.java 2 // Application shows two threads manipulating an unsynchronized buffer. 3 import java.util.concurrent.executorservice; 4 import java.util.concurrent.executors; 5 6 public class SharedBufferTest 7 8 public static void main( String[] args ) 9 10 // create new thread pool with two threads 11 ExecutorService application = Executors.newFixedThreadPool( 2 ); 12 13 // create UnsynchronizedBuffer to store ints 14 Buffer sharedlocation = new UnsynchronizedBuffer(); 15 Create shared UnsynchronizedBuffer for producer and consumer to use 57
16 System.out.println( "Action\t\tValue\tProduced\tConsumed" ); 17 System.out.println( "------\t\t-----\t--------\t--------\n" ); 18 19 // try to start producer and consumer giving each of them access 20 // to sharedlocation 21 try 22 23 application.execute( new Producer( sharedlocation ) ); 24 application.execute( new Consumer( sharedlocation ) ); 25 // end try 26 catch ( Exception exception ) 27 28 exception.printstacktrace(); 29 // end catch 30 31 application.shutdown(); // terminate application when threads end 32 // end main 33 // end class SharedBufferTest Pass shared buffer to both producer and consumer 58
Action Value Produced Consumed ------ ----- -------- -------- Producer writes 1 1 Producer writes 2 3 Producer writes 3 6 Consumer reads 3 3 Producer writes 4 10 Consumer reads 4 7 Producer writes 5 15 Producer writes 6 21 Producer writes 7 28 Consumer reads 7 14 Consumer reads 7 21 Producer writes 8 36 Consumer reads 8 29 Consumer reads 8 37 Producer writes 9 45 Producer writes 10 55 Producer done producing. Terminating Producer. Consumer reads 10 47 Consumer reads 10 57 Consumer reads 10 67 Consumer reads 10 77 Consumer read values totaling 77. Terminating Consumer. 59
Action Value Produced Consumed ------ ----- -------- -------- Consumer reads -1-1 Producer writes 1 1 Consumer reads 1 0 Consumer reads 1 1 Consumer reads 1 2 Consumer reads 1 3 Consumer reads 1 4 Producer writes 2 3 Consumer reads 2 6 Producer writes 3 6 Consumer reads 3 9 Producer writes 4 10 Consumer reads 4 13 Producer writes 5 15 Producer writes 6 21 Consumer reads 6 19 Consumer read values totaling 19. Terminating Consumer. Producer writes 7 28 Producer writes 8 36 Producer writes 9 45 Producer writes 10 55 Producer done producing. Terminating Producer. 60
Συγχρονισμός με Monitors και Monitor Locks Μια μέθοδος μπορεί να οριστεί σαν συγχρονισμένη (synchronized) απλά χρησιμοποιώντας το keyword synchronized στον ορισμό της μεθόδου, όπως πιο κάτω: public synchronized void mysyncfunction()... Επίσης, κάποιες εντολές (κομμάτι κώδικα) μπορεί να οριστούν σαν συγχρονισμένες (synchronized), όπως πιο κάτω, ώστε να μην μπορούν να εκτελεστούν ταυτοχρόνως με άλλο συγχρονισμένο κώδικα (μεθόδους ή εντολές) στο ίδιο αντικείμενο: synchronized ( <object>)... 61
Ορίζοντας συγχρονισμένη μια μέθοδο ή κάποιες εντολές σημαίνει ότι όταν ένα thread χρησιμοποιεί συγχρονισμένο κώδικα σε ένα αντικείμενο, το οποίο σημαίνει ότι έχει αποκτήσει την κλειδαριά του, δεν μπορεί ένα άλλο thread να πάρει τον έλεγχο και να χρησιμοποιήσει στο ίδιο αντικείμενο συγχρονισμένο κώδικα, αφού θεωρείται κλειδωμένο από τη στιγμή που το πρώτο thread κατέχει ήδη την κλειδαριά. Όλα τα άλλα threads που θέλουν να εκτελέσουν στο ίδιο αντικείμενο συγχρονισμένο κώδικα μπλοκάρονται και θα πρέπει να περιμένουν να τελειώσει το πρώτο thread και να ελευθερώσει τη κλειδαριά. Η χρήση συγχρονισμού έχει αρνητικές συνέπειες στην απόδοση ενός προγράμματος και θα πρέπει να χρησιμοποιείται μόνο σε κρίσιμα τμήματα του κώδικα για αποφυγή σφαλμάτων. 62
Συγχρονισμένες μέθοδοι (synchronized methods) περιμένουν μόνο για συγχρονισμένες μεθόδους στο ίδιο αντικείμενο. Συνεπώς, μια συγχρονισμένη μέθοδος (synchronized method) μπορεί να κληθεί σε άλλο αντικείμενο (instance) της ίδιας τάξης. Επίσης, μπορούν να κληθούν χωρίς κανένα περιορισμό μέθοδοι που δεν είναι συγχρονισμένες στο ίδιο αντικείμενο, ασχέτως με το εάν ενδεχομένως μια συγχρονισμένη μέθοδος (synchronized method) έχει κληθεί στο ίδιο αντικείμενο και εκτελείται από κάποιο άλλο thread. Αυτός ο τρόπος συγχρονισμού βασίζεται με μια ειδική κλειδαριά, τη monitor, που έχει το κάθε αντικείμενο την οποία κληρονομεί από τη τάξη Object. Όταν ένα thread προσπαθήσει να εκτελέσει μια συγχρονισμένη μέθοδο πρέπει να πάρει την κλειδαριά του συγκεκριμένου αντικειμένου για να μπορέσει να προχωρήσει. 63
Μέθοδοι wait(), notify(), notifyall(), της Object Κάθε αντικείμενο έχει ένα monitor το οποίο κληρονομεί από την Object Το monitor επιτρέπει μόνο ένα thread να εκτελεί συγχρονισμένο κώδικα (synchronized methods and statements) στο ίδιο αντικείμενο σε οποιαδήποτε χρονική στιγμή Ένα thread που περιμένει να αποκτήσει την κλειδαριά (monitor) ενός αντικειμένου, λόγω του ότι ήδη την κατέχει κάποιο άλλο thread, μπλοκάρεται (blocked state) Η μέθοδος wait() της τάξης Object θέτει ένα thread σε κατάσταση αναμονής (waiting state) Η μέθοδος notify() της τάξης Object ειδοποιεί ένα thread που αναμένει Η μέθοδος notifyall() της τάξης Object ειδοποιεί ή άλλως ξυπνά όλα τα threads που αναμένουν 64
Ένα thread μπορεί να καλέσει συγχρονισμένο κώδικα ενός αντικειμένου ενώ έχει ήδη το κλειδί του, δηλαδή μπορεί να επαναποκτήσει κλειδί το οποίο έχει. Έτσι μπορεί να εκτελέσει μια συγχρονισμένη μέθοδο που καλείται από μια άλλη συγχρονισμένη μέθοδο. Είναι λάθος ένα thread να καλέσει τις μεθόδους wait(), notify() και notifyall() σε ένα αντικείμενο χωρίς να κατέχει την κλειδαριά για αυτό. Τέτοια προσπάθεια προκαλεί ένα IllegalMonitorStateException. 65
Αδιέξοδο - Deadlock Συμβαίνει όταν ένα waiting thread (π.χ. το thread1) δεν μπορεί να προχωρήσει γιατί περιμένει είτε άμεσα ή έμμεσα ένα άλλο thread (π.χ. thread2), ενώ το thread2 δεν μπορεί να προχωρήσει γιατί περιμένει είτε άμεσα ή έμμεσα το thread1. Έχοντας δύο threads να περιμένουν το ένα το άλλο, οι συνθήκες που θα επέτρεπαν στο κάθε thread να εκτελεστεί ποτέ δεν θα συμβούν. Συνήθως συμβαίνει όταν δύο threads προσπαθούν ταυτόχρονα να τρέχουν συγχρονισμένο κώδικα στο ίδιο αντικείμενο μπλοκάροντας το ένα το άλλο. 66
Παράδειγμα: Παραγωγού/Καταναλωτή (Producer/Consumer) με συγχρονισμό (synchronization) 1 // Fig. 23.6: Buffer.java 2 // Buffer interface specifies methods called by Producer and Consumer. 3 4 public interface Buffer 5 6 public void set( int value ); // place int value into Buffer 7 public int get(); // return int value from Buffer 8 // end interface Buffer 67
1 // Fig. 23.19: SynchronizedBuffer.java 2 // SynchronizedBuffer synchronizes access to a single shared integer. 3 4 public class SynchronizedBuffer implements Buffer 5 6 private int buffer = -1; // shared by producer and consumer threads 7 private boolean occupied = false; // count of occupied buffers 8 9 // place value into buffer 10 public synchronized void set( int value ) 11 12 // while there are no empty locations, place thread in waiting state 13 while ( occupied ) 14 15 // output thread information and buffer information, then wait 16 try 17 18 System.out.println( "Producer tries to write." ); 19 displaystate( "Buffer full. Producer waits." ); 20 wait(); 21 // end try 22 catch ( InterruptedException exception ) 23 24 exception.printstacktrace(); 25 // end catch 26 // end while 27 28 buffer = value; // set new buffer value 29 Declare a synchronized set method Wait until buffer is empty Set buffer value 68
30 // indicate producer cannot store another value 31 // until consumer retrieves current buffer value 32 occupied = true; 33 34 displaystate( "Producer writes " + buffer ); 35 36 notify(); // tell waiting thread to enter runnable state 37 // end method set; releases lock on SynchronizedBuffer 38 39 // return value from buffer 40 public synchronized int get() 41 42 // while no data to read, place thread in waiting state 43 while (!occupied ) 44 45 // output thread information and buffer information, then wait 46 try 47 48 System.out.println( "Consumer tries to read." ); 49 displaystate( "Buffer empty. Consumer waits." ); 50 wait(); 51 // end try 52 catch ( InterruptedException exception ) 53 54 exception.printstacktrace(); 55 // end catch 56 // end while Declare synchronized get method Buffer is now occupied Notify waiting thread that it may now read a value Wait until buffer is full 69
58 // indicate that producer can store another value 59 // because consumer just retrieved buffer value 60 occupied = false; 61 62 int readvalue = buffer; // store value in buffer 63 displaystate( "Consumer reads " + readvalue ); 64 65 notify(); // tell waiting thread to enter runnable state 66 67 return readvalue; 68 // end method get; releases lock on SynchronizedBuffer 69 70 // display current operation and buffer state 71 public void displaystate( String operation ) 72 73 System.out.printf( "%-40s%d\t\t%b\n\n", operation, buffer, 74 occupied ); 75 // end method displaystate 76 // end class SynchronizedBuffer Buffer is now empty Notify thread it may now write to buffer 70
1 // Fig. 23.7: Producer.java 2 // Producer's run method stores the values 1 to 10 in buffer. 3 import java.util.random; 4 5 public class Producer implements Runnable 6 7 private static Random generator = new Random(); 8 private Buffer sharedlocation; // reference to shared object 9 10 // constructor 11 public Producer( Buffer shared ) 12 13 sharedlocation = shared; 14 // end Producer constructor 15 16 // store values from 1 to 10 in sharedlocation 17 public void run() 18 19 int sum = 0; 20 Implement the runnable interface so that producer will run in a separate thread Declare run method to satisfy interface 71
21 for ( int count = 1; count <= 10; count++ ) 22 23 try // sleep 0 to 3 seconds, then place value in Buffer 24 25 Thread.sleep( generator.nextint( 3000 ) ); // sleep thread 26 sharedlocation.set( count ); // set value in buffer 27 sum += count; // increment sum of values 28 System.out.printf( "\t%2d\n", sum ); 29 // end try 30 // if sleeping thread interrupted, print stack trace 31 catch ( InterruptedException exception ) 32 33 exception.printstacktrace(); 34 // end catch 35 // end for 36 37 System.out.printf( "\n%s\n%s\n", "Producer done producing.", 38 "Terminating Producer." ); 39 // end method run 40 // end class Producer Sleep for up to 3 seconds 72
1 // Fig. 23.8: Consumer.java 2 // Consumer's run method loops ten times reading a value from buffer. 3 import java.util.random; 4 5 public class Consumer implements Runnable 6 7 private static Random generator = new Random(); 8 private Buffer sharedlocation; // reference to shared object 9 10 // constructor 11 public Consumer( Buffer shared ) 12 13 sharedlocation = shared; 14 // end Consumer constructor 15 16 // read sharedlocation's value four times and sum the values 17 public void run() 18 19 int sum = 0; 20 Implement the runnable interface so that producer will run in a separate thread Declare run method to satisfy interface 73
21 for ( int count = 1; count <= 10; count++ ) 22 23 // sleep 0 to 3 seconds, read value from buffer and add to sum 24 try 25 26 Thread.sleep( generator.nextint( 3000 ) ); 27 sum += sharedlocation.get(); 28 System.out.printf( "\t\t\t%2d\n", sum ); 29 // end try 30 // if sleeping thread interrupted, print stack trace 31 catch ( InterruptedException exception ) 32 33 exception.printstacktrace(); 34 // end catch 35 // end for 36 37 System.out.printf( "\n%s %d.\n%s\n", 38 "Consumer read values totaling", sum, "Terminating Consumer." ); 39 // end method run 40 // end class Consumer Sleep for up to 3 seconds 74
1 // Fig 23.20: SharedBufferTest2.java 2 // Application shows two threads manipulating a synchronized buffer. 3 import java.util.concurrent.executorservice; 4 import java.util.concurrent.executors; 5 6 public class SharedBufferTest2 7 8 public static void main( String[] args ) 9 10 // create new thread pool with two threads 11 ExecutorService application = Executors.newFixedThreadPool( 2 ); 12 13 // create SynchronizedBuffer to store ints 14 Buffer sharedlocation = new SynchronizedBuffer(); 15 16 System.out.printf( "%-40s%s\t\t%s\n%-40s%s\n\n", "Operation", 17 "Buffer", "Occupied", "---------", "------\t\t--------" ); 18 19 try // try to start producer and consumer 20 21 application.execute( new Producer( sharedlocation ) ); 22 application.execute( new Consumer( sharedlocation ) ); 23 // end try 24 catch ( Exception exception ) 25 26 exception.printstacktrace(); 27 // end catch Create a SynchronizedBuffer for use in producer and consumer Execute the producer and consumer in separate threads 75
29 application.shutdown(); 30 // end main 31 // end class SharedBufferTest2 Operation Buffer Occupied --------- ------ -------- Consumer tries to read. Buffer empty. Consumer waits. -1 false Producer writes 1 1 true Consumer reads 1 1 false Consumer tries to read. Buffer empty. Consumer waits. 1 false Producer writes 2 2 true Consumer reads 2 2 false Producer writes 3 3 true Consumer reads 3 3 false Consumer tries to read. Buffer empty. Consumer waits. 3 false Producer writes 4 4 true Consumer reads 4 4 false Consumer tries to read. Buffer empty. Consumer waits. 4 false Producer writes 5 5 true Consumer reads 5 5 false 76
Producer writes 6 6 true Consumer reads 6 6 false Consumer tries to read. Buffer empty. Consumer waits. 6 false Producer writes 7 7 true Consumer reads 7 7 false Consumer tries to read. Buffer empty. Consumer waits. 7 false Producer writes 8 8 true Consumer reads 8 8 false Producer writes 9 9 true Producer tries to write. Buffer full. Producer waits. 9 true Consumer reads 9 9 false Producer writes 10 10 true Producer done producing. Terminating Producer. Consumer reads 10 10 false Consumer read values totaling 55. Terminating Consumer. 77
J2SE5.0: Διασύνδεση (interface) Lock για συγχρονισμό Η μέθοδος lock() αποκτά την κλειδαριά (lock), επιβάλλοντας αμοιβαίο αποκλεισμό (mutual exclusion) Η μέθοδος unlock() ελευθερώνει την κλειδαριά (lock) Η τάξη ReentrantLock υλοποιεί τη διασύνδεση (interface) Lock Χρησιμοποιώντας μία κλειδαριά (Lock) με δίκαιο τρόπο διαχείρισης (fairness policy) βοηθά στην αποφυγή επ άπειρο αποκλεισμού, αλλά μπορεί να μειώσει σημαντικά το συνολικό επίπεδο απόδοσης (overall efficiency) ενός προγράμματος και έτσι πρέπει να χρησιμοποιούνται μόνο όταν είναι απαραίτητο. 78
Παρατηρήσεις για συγχρονισμό (Synchronization) Μεταβλητές προϋποθέσεων (Condition variables) Αν ένα thread κρατά μια κλειδαριά δεν μπορεί να συνεχίσει το στόχο του έως η προϋπόθεση ικανοποιηθεί, το thread μπορεί να περιμένει μία μεταβλητή προϋποθέσεων (Condition variables) Δημιουργείται καλώντας τη μέθοδο newcondition() της τάξης Lock Αντιπροσωπεύεται από ένα αντικείμενο το οποίο υλοποιεί τη διασύνδεση (interface) Condition Διασύνδεση (interface) Condition Ορίζει τις μεθόδους: o o o await(): για να αναγκάσει ένα thread να περιμένει signal(): για να "ξυπνήσει" (wake up) ένα thread που περιμένει signalall(): για να "ξυπνήσει" (wake up) όλα τα threads που περιμένουν 79
Αδιέξοδο - Deadlock Έχοντας δύο threads να περιμένουν το ένα το άλλο, οι συνθήκες που θα επέτρεπαν στο κάθε thread να εκτελεστεί ποτέ δεν θα συμβούν. Το κλείδωμα το οποίο συμβαίνει με την εκτέλεση της μεθόδου lock() μπορεί να οδηγήσει σε deadlock αν τα locks δεν ελευθερωθούν ποτέ. Κλήσεις στη μέθοδο unlock() πρέπει να τοποθετούνται σε finally blocks ώστε να είναι σίγουρο ότι τα locks θα ελευθερωθούν τελικά αποφεύγοντας προβλήματα με deadlocks. 80
Παρατηρήσεις Όταν πολλά threads τροποποιούν ένα κοινό αντικείμενο χρησιμοποιώντας locks, αν ένα thread καλέσει τη μέθοδο await() για να εισέλθει σε κατάσταση waiting για μία condition variable, πρέπει οπωσδήποτε ένα άλλο thread να καλέσει τη μέθοδο signal() του Condition για να μπορέσει το thread που περιμένει το condition variable να επιστρέψει σε runnable state. Αν πολλά threads περιμένουν ένα condition variable ένα άλλο thread μπορεί να καλέσει τη μέθοδο signalall() της τάξης Condition για να δώσει ευκαιρία σε όλα τα waiting threads να εκτελέσουν τους στόχους τους. Αν αυτό δεν συμβεί τότε μπορεί να συμβεί επ αόριστο αναβολή εκτέλεσης (indefinite postponement) ή αδιέξοδο (deadlock). Αν ένα thread καλέσει μία από τις μεθόδους await(), signal(), ή signalall() σε ένα condition variable χωρίς να έχει αποκτήσει το αντίστοιχο lock για εκείνο το condition variable προκαλείται ένα IllegalMonitorStateException. 81
Παράδειγμα: 2 ος Τρόπος υλοποίησης παραγωγού/καταναλωτή (Producer/Consumer) με συγχρονισμό (synchronization) 1 // Fig. 23.6: Buffer.java 2 // Buffer interface specifies methods called by Producer and Consumer. 3 4 public interface Buffer 5 6 public void set( int value ); // place int value into Buffer 7 public int get(); // return int value from Buffer 8 // end interface Buffer 82
1 // Fig. 23.11: SynchronizedBuffer.java 2 // SynchronizedBuffer synchronizes access to a single shared integer. 3 import java.util.concurrent.locks.lock; 4 import java.util.concurrent.locks.reentrantlock; 5 import java.util.concurrent.locks.condition; 6 7 public class SynchronizedBuffer implements Buffer 8 9 // Lock to control synchronization with this buffer 10 private Lock accesslock = new ReentrantLock(); 11 12 // conditions to control reading and writing 13 private Condition canwrite = accesslock.newcondition(); 14 private Condition canread = accesslock.newcondition(); 15 16 private int buffer = -1; // shared by producer and consumer threads 17 private boolean occupied = false; // whether buffer is occupied 18 19 // place int value into buffer 20 public void set( int value ) 21 22 accesslock.lock(); // lock this object 23 Create ReentrantLock for mutual exclusion Create two Condition variables; one for writing and one for reading Buffer shared by producer and consumer Try to obtain the lock before setting the value of the shared data 83
24 // output thread information and buffer information, then wait 25 try 26 27 // while buffer is not empty, place thread in waiting state 28 while ( occupied ) 29 30 System.out.println( "Producer tries to write." ); 31 displaystate( "Buffer full. Producer waits." ); 32 canwrite.await(); // wait until buffer is empty 33 // end while 34 35 buffer = value; // set new buffer value 36 37 // indicate producer cannot store another value 38 // until consumer retrieves current buffer value 39 occupied = true; 40 Producer waits until buffer is empty 84
41 displaystate( "Producer writes " + buffer ); 42 43 // signal thread waiting to read from buffer 44 canread.signal(); 45 // end try 46 catch ( InterruptedException exception ) 47 48 exception.printstacktrace(); 49 // end catch 50 finally 51 52 accesslock.unlock(); // unlock this object 53 // end finally 54 // end method set 55 56 // return value from buffer 57 public int get() 58 59 int readvalue = 0; // initialize value read from buffer 60 accesslock.lock(); // lock this object 61 Signal consumer that it may read a value Release lock on shared data Acquire lock before reading a value 85
62 // output thread information and buffer information, then wait 63 try 64 65 // while no data to read, place thread in waiting state 66 while (!occupied ) 67 68 System.out.println( "Consumer tries to read." ); 69 displaystate( "Buffer empty. Consumer waits." ); 70 canread.await(); // wait until buffer is full 71 // end while 72 73 // indicate that producer can store another value 74 // because consumer just retrieved buffer value 75 occupied = false; 76 77 readvalue = buffer; // retrieve value from buffer 78 displaystate( "Consumer reads " + readvalue ); 79 Consumer waits until buffer contains data to read 86
80 // signal thread waiting for buffer to be empty 81 canwrite.signal(); 82 // end try 83 // if waiting thread interrupted, print stack trace 84 catch ( InterruptedException exception ) 85 86 exception.printstacktrace(); 87 // end catch 88 finally 89 90 accesslock.unlock(); // unlock this object 91 // end finally 92 93 return readvalue; 94 // end method get 95 96 // display current operation and buffer state 97 public void displaystate( String operation ) 98 99 System.out.printf( "%-40s%d\t\t%b\n\n", operation, buffer, 100 occupied ); 101 // end method displaystate 102 // end class SynchronizedBuffer Signal producer that it can write to buffer Release lock on shared data 87
1 // Fig. 23.7: Producer.java 2 // Producer's run method stores the values 1 to 10 in buffer. 3 import java.util.random; 4 5 public class Producer implements Runnable 6 7 private static Random generator = new Random(); 8 private Buffer sharedlocation; // reference to shared object 9 10 // constructor 11 public Producer( Buffer shared ) 12 13 sharedlocation = shared; 14 // end Producer constructor 15 16 // store values from 1 to 10 in sharedlocation 17 public void run() 18 19 int sum = 0; 20 Implement the runnable interface so that producer will run in a separate thread Declare run method to satisfy interface 88
21 for ( int count = 1; count <= 10; count++ ) 22 23 try // sleep 0 to 3 seconds, then place value in Buffer 24 25 Thread.sleep( generator.nextint( 3000 ) ); // sleep thread 26 sharedlocation.set( count ); // set value in buffer 27 sum += count; // increment sum of values 28 System.out.printf( "\t%2d\n", sum ); 29 // end try 30 // if sleeping thread interrupted, print stack trace 31 catch ( InterruptedException exception ) 32 33 exception.printstacktrace(); 34 // end catch 35 // end for 36 37 System.out.printf( "\n%s\n%s\n", "Producer done producing.", 38 "Terminating Producer." ); 39 // end method run 40 // end class Producer Sleep for up to 3 seconds 89
1 // Fig. 23.8: Consumer.java 2 // Consumer's run method loops ten times reading a value from buffer. 3 import java.util.random; 4 5 public class Consumer implements Runnable 6 7 private static Random generator = new Random(); 8 private Buffer sharedlocation; // reference to shared object 9 10 // constructor 11 public Consumer( Buffer shared ) 12 13 sharedlocation = shared; 14 // end Consumer constructor 15 16 // read sharedlocation's value four times and sum the values 17 public void run() 18 19 int sum = 0; 20 Implement the runnable interface so that producer will run in a separate thread Declare run method to satisfy interface 90
21 for ( int count = 1; count <= 10; count++ ) 22 23 // sleep 0 to 3 seconds, read value from buffer and add to sum 24 try 25 26 Thread.sleep( generator.nextint( 3000 ) ); 27 sum += sharedlocation.get(); 28 System.out.printf( "\t\t\t%2d\n", sum ); 29 // end try 30 // if sleeping thread interrupted, print stack trace 31 catch ( InterruptedException exception ) 32 33 exception.printstacktrace(); 34 // end catch 35 // end for 36 37 System.out.printf( "\n%s %d.\n%s\n", 38 "Consumer read values totaling", sum, "Terminating Consumer." ); 39 // end method run 40 // end class Consumer Sleep for up to 3 seconds 91
1 // Fig 23.12: SharedBufferTest2.java 2 // Application shows two threads manipulating a synchronized buffer. 3 import java.util.concurrent.executorservice; 4 import java.util.concurrent.executors; 5 6 public class SharedBufferTest2 7 8 public static void main( String[] args ) 9 10 // create new thread pool with two threads 11 ExecutorService application = Executors.newFixedThreadPool( 2 ); 12 13 // create SynchronizedBuffer to store ints 14 Buffer sharedlocation = new SynchronizedBuffer(); 15 Create SynchronizedBuffer to be shared between producer and consumer 92
16 System.out.printf( "%-40s%s\t\t%s\n%-40s%s\n\n", "Operation", 17 "Buffer", "Occupied", "---------", "------\t\t--------" ); 18 19 try // try to start producer and consumer 20 21 application.execute( new Producer( sharedlocation ) ); 22 application.execute( new Consumer( sharedlocation ) ); 23 // end try 24 catch ( Exception exception ) 25 26 exception.printstacktrace(); 27 // end catch 28 29 application.shutdown(); 30 // end main 31 // end class SharedBufferTest2 Execute the producer and consumer in separate threads 93
Operation Buffer Occupied --------- ------ -------- Producer writes 1 1 true Producer tries to write. Buffer full. Producer waits. 1 true Consumer reads 1 1 false Producer writes 2 2 true Producer tries to write. Buffer full. Producer waits. 2 true Consumer reads 2 2 false Producer writes 3 3 true Consumer reads 3 3 false Producer writes 4 4 true Consumer reads 4 4 false Consumer tries to read. Buffer empty. Consumer waits. 4 false Producer writes 5 5 true Consumer reads 5 5 false Consumer tries to read. Buffer empty. ΠΠΜ Consumer 500: Προχωρημένη waits. Ανάπτυξη Λογισμικού Εφαρμογών 5 Μηχανικής false 94
Producer writes 6 6 true Consumer reads 6 6 false Producer writes 7 7 true Consumer reads 7 7 false Producer writes 8 8 true Consumer reads 8 8 false Producer writes 9 9 true Consumer reads 9 9 false Producer writes 10 10 true Producer done producing. Terminating Producer. Consumer reads 10 10 false Consumer read values totaling 55. Terminating Consumer. 95
Multithreading και GUI Τα GUI συστατικά του Swing δεν είναι thread safe Όλες οι τροποποιήσεις και αλλαγές ενός Swing GUI πρέπει να γίνονται μέσα από το event-dispatching thread o Αυτό επιτυγχάνεται εύκολα με τη στατική μέθοδο invokelater() της τάξης SwingUtilities στην οποία δίνεται σαν παράμετρος ένα Runnable αντικείμενο 96
Χρήσιμες Πληροφορίες Concurrent Programming in Java, Second Edition, Doug Lea, written for intermediate and advanced threads programmers. Java Threads, Third Edition, Scott Oaks and Henry Wong (O'Reilly, 2004) Java Concurrency in Practice, Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, Doug Lea (Addison Wesley, available early 2006) Sun s Java Tutorial: Threads 97
Παράδειγμα προσομοίωσης με γραφικά import javax.swing.*; import java.awt.event.*; import java.awt.*; PS5_2Threaded.java public class PS5_2Threaded public static void main(string args[]) javax.swing.swingutilities.invokelater(new Runnable() public void run() new ShapePanel("Drawing some shapes"); ); 98
class ShapePanel extends JFrame implements Runnable private JPanel buttonspanel; private JButton buttonstart, buttonstop, buttoncontinue; private JPanel shapepanel, colorpanel; private MyJPanel canvas; private JButton computebutton; private JRadioButton circleradiobutton; private JRadioButton rectangleradiobutton; private JRadioButton squareradiobutton; private JRadioButton triangleradiobutton; private ButtonGroup buttongroup; private int step; private final int STEPS=100000 ; private Thread animatorthread ; private boolean frozen=true ; 99
ShapePanel(String s) super(s); // Start - Stop - Continue buttonspanel = new JPanel(); buttonspanel.setborder(borderfactory.createemptyborder(40, 20, 20, 30)); buttonspanel.setlayout(new GridLayout(1,3)); buttonstart = new JButton("Start", new ImageIcon("start.gif")); buttonstart.addactionlistener(new MyInnerStartEventHandler()); buttonstart.setmnemonic('s'); buttonspanel.add(buttonstart); buttonstop = new JButton("Stop", new ImageIcon("stop.gif")); buttonstop.addactionlistener(new MyInnerStopEventHandler()); buttonstop.setmnemonic('t'); buttonstop.setenabled(false); buttonspanel.add(buttonstop); buttoncontinue = new JButton("Continue", new ImageIcon("continue.gif")); buttoncontinue.addactionlistener(new MyInnerContinueEventHandler()); buttoncontinue.setenabled(false); buttoncontinue.setmnemonic('c'); buttonspanel.add(buttoncontinue); 100
add(buttonspanel,borderlayout.north); // Canvas canvas = new MyJPanel(); shapepanel = new JPanel(); colorpanel = new JPanel(); // Shape selection: Circle - Rectangle - Square - Triangle shapepanel.setlayout(new GridLayout(1,4)); circleradiobutton = new JRadioButton("Circle"); circleradiobutton.addactionlistener(new MyInnerCircleEventHandler()); rectangleradiobutton = new JRadioButton("Rectangle"); rectangleradiobutton.addactionlistener(new MyInnerRectangleEventHandler()); squareradiobutton = new JRadioButton("Square"); squareradiobutton.addactionlistener(new MyInnerSquareEventHandler()); triangleradiobutton = new JRadioButton("Triangle"); triangleradiobutton.addactionlistener(new MyInnerTriangleEventHandler()); buttongroup = new ButtonGroup(); buttongroup.add(circleradiobutton); buttongroup.add(rectangleradiobutton); buttongroup.add(squareradiobutton); buttongroup.add(triangleradiobutton); circleradiobutton.setselected(true); canvas.setshape("circle"); 101
shapepanel.add(circleradiobutton); shapepanel.add(rectangleradiobutton); shapepanel.add(squareradiobutton); shapepanel.add(triangleradiobutton); add(canvas, BorderLayout.CENTER); add(shapepanel, BorderLayout.SOUTH); add(colorpanel, BorderLayout.EAST); setbounds(100,50,450,600); setdefaultlookandfeeldecorated(true); setdefaultcloseoperation(jframe.exit_on_close); setvisible(true); private class MyInnerStartEventHandler implements ActionListener public void actionperformed(actionevent ae) System.out.println("Starting the simulation") ; buttonstart.setenabled(false); buttonstop.setenabled(true); startsimulation(); 102
private class MyInnerStopEventHandler implements ActionListener public void actionperformed(actionevent ae) System.out.println("Stoping the simulation") ; buttonstop.setenabled(false); buttoncontinue.setenabled(true); stopsimulation(); private class MyInnerContinueEventHandler implements ActionListener public void actionperformed(actionevent ae) System.out.println("Continuing the simulation") ; buttonstop.setenabled(true); buttoncontinue.setenabled(false); continuesimulation(); 103
private class MyInnerCircleEventHandler implements ActionListener public void actionperformed(actionevent ae) canvas.setshape("circle"); repaint(); private class MyInnerRectangleEventHandler implements ActionListener public void actionperformed(actionevent ae) canvas.setshape("rectangle"); repaint(); private class MyInnerSquareEventHandler implements ActionListener public void actionperformed(actionevent ae) canvas.setshape("square"); repaint(); 104
private class MyInnerTriangleEventHandler implements ActionListener public void actionperformed(actionevent ae) canvas.setshape("triangle"); repaint(); void startsimulation() frozen = false ; step = 0; start() ; public void stopsimulation() repaint() ; animatorthread=null ; frozen=true ; public void continuesimulation() frozen=false ; start() ; 105
public void start() if(!frozen) // Start animating the simulated system if(animatorthread==null) animatorthread=new Thread(this) ; animatorthread.start() ; public void run() int skipnumber=20; Thread.currentThread().setPriority(Thread.MIN_PRIORITY) ; while(thread.currentthread()==animatorthread && step<steps) timestep(); if(step%skipnumber==0) repaint() ; try Thread.sleep(10) ; catch(interruptedexception e) System.out.println("InterruptedException e: " + e); step++; 106
public void timestep() canvas.setdimension((double)step/steps*200); class MyJPanel extends JPanel private String selectedshape="circle"; private double dimension; MyJPanel() super(); void setdimension(double d) dimension = d; void setshape(string s) selectedshape = s; System.out.println(" selectedshape : " +selectedshape); 107
public void paintcomponent(graphics g) super.paintcomponent(g); setbackground(color.black); if(selectedshape.equals("circle")) g.setcolor(color.yellow); g.filloval(100,100,(int)(dimension),(int)(dimension)); else if(selectedshape.equals("square")) g.setcolor(color.green); g.fillrect(100,100,(int)(dimension),(int)(dimension)); else if(selectedshape.equals("rectangle")) g.setcolor(color.red); g.fillrect(50,100,(int)(1.75*dimension),(int)(0.75*dimension)); else if(selectedshape.equals("triangle")) int x[] = (int)(0.5*dimension), (int)(1.5*dimension), (int)(dimension) ; int y[] = (int)(1.5*dimension), (int)(1.5*dimension), (int)(0.5*dimension) ; g.setcolor(color.cyan); g.fillpolygon(x,y,3); 108
109