Εισαγωγή στην Java Module 9: Threads 15/03/2004 Prepared by Chris Panayiotou for EPL 602 1
Εισαγωγή στα Threads Συχνά χρειάζεται, να υπάρχουν ανεξάρτητες διεργασίες σε ένα πρόγραµµα separate, independently-running subtasks. Τα Objects παρέχουν ένα τρόπο διαχωρισµού του προγράµµατος σε ανεξάρτητα κοµµάτια (independent sections). Κάθε µια από αυτές τις διεργασίες ονοµάζεται thread - νήµα. Ο τρόπος προγραµµατισµού είναι ο ίδιος όπως στην περίπτωση που κάθε διεργασία τρέχει µόνη της έχοντας το CPU για αυτήν µόνο. 15/03/2004 EPL 602 2
Εισαγωγή στα Threads Μια διεργασία process είναι αυτό-περιεχόµενη (self-contained) στο δικό της address space. Σε περιπτώσεις πολυ-νηµατικών διεργασιών multitasking το λειτουργικό σύστηµα είναι ικανό να χειρίζεται περισσότερες από µια διεργασίες process (program) ταυτόχρονα. Ένα thread αποτελεί σειριακή ροή sequential flow έλεγχου σε µέσα στην διεργασία. Μια διεργασία µπορείναέχειπολλάthreads τα οποία εκτελούνται ταυτόχρονα. 15/03/2004 EPL 602 3
Εισαγωγή στα Threads Υπάρχουν αρκετές χρήσεις του multithreading Όταν ένα µέρος του προγράµµατος περιµένει να συµβεί κάποιο γεγονός εν θέλεις να καθυστερεί το πρόγραµµα σου µέχρι να γίνει το γεγονός αυτό ηµιουργώντας ένα thread το οποίο αναµένει το συγκεκριµένο γεγονός και το αφήνεις να τρέχει ανεξάρτητα µε τουπόλοιποπρόγραµµα σου. Συνηθισµένο παράδειγµα είναι το κουµπί Quit Ένας από τους πιο σοβαρούς λόγους που χρειάζεται το multithreading είναι για παραγωγή a responsive user interface. 15/03/2004 EPL 602 4
Κληρονοµικότητα από το Thread Ο πίο απλός τρόπος να δηµιουργήσεις ένα thread είναι να επεκτείνεις την class Thread, η οποία παρέχει όλα αναγκαία χαρακτηριστικά για την δηµιουργία και εκτέλεση των threads. Η πιο σηµαντική µέθοδος στο Thread είναι η run( ), η οποία πρέπει να δηλωθεί (overridden) για να αναγκάσεις το Thread να εκτελέσει τις δικές σου εντολές. Ως εκ τούτου στη run( ) ορίζουµε τον κώδικα ο οποίος θα εκτελεστεί ταυτόχρονα simultaneously µε τα άλλα threads στο πρόγραµµα 15/03/2004 EPL 602 5
Το πρώτο Thread Η µέθοδος run( ) πάνταπρέπειναέχειεικονικάκάποιο βρόχο ο οποίος συνεχίζει την εκτέλεσης του µέχρι το thread να µην είναι πλέον αναγκαίο. Στο παράδειγµα στηνmain( ) µπορείτε να δείτε να δηµιουργούνται αρκετά threads και να τέχουν. Σηµαντική µέθοδος του Thread είναι η start( ) (αρχικοποιεί το thread και µετά καλεί την run( )). Τα βήµατα έχουν ώς εξής: Καλείτε ο constructor και δηµιουργεί το αντικείµενο. Η µέθοδος start( ) αρχικοποιεί το thread και καλεί την run( ). Αν δεν καλέσετε την µέθοδο start( ) το thread δεν θα ξεκινήσει να τρέχει. 15/03/2004 EPL 602 6
Παράδειγµα public class SimpleThread extends Thread { private int countdown = 5; private int threadnumber; private static int threadcount = 0; public SimpleThread() { threadnumber = ++threadcount; System.out.println("Making " + threadnumber); public void run() { while(true) { System.out.println("Thread " + threadnumber + "(" + countdown + ")"); if(--countdown == 0) return; public static void main(string[] args) { for(int i = 0; i < 5; i++) new SimpleThread().start(); System.out.println("All Threads Started"); 15/03/2004 EPL 602 7
Daemon Threads Ένα daemon thread είναι κάποιο το οποίο είναι υπεύθυνο για τον έλεγχο των threads. Λειτουργεί στο background ενόσω το πρόγραµµα βρίσκεται σε λειτουργία. εν είναι µέρος της οντότητας του προγράµµατος, γι αυτό και όταν όλα τα non-daemon threads τερµατίσουν το πρόγραµµα τελειώνει. Τρόπος για να δείς άν ένα thread είναι daemon είναι µε την µέθοδο isdaemon( ) Μπορείςναενεργοποιήσειςτοdaemonhood ενός thread µε την µέθοδο setdaemon( ). [on,off] Αν ένα thread είναι daemon, τότε κάθε thread που δηµιουργεί είναι αυτόµατα και αυτό daemon. 15/03/2004 EPL 602 8
Παράδειγµα Daemon Threads class Daemon extends Thread { private static final int SIZE = 10; private Thread[] t = new Thread[SIZE]; public Daemon() { setdaemon(true); start(); public void run() { for(int i = 0; i < SIZE; i++) t[i] = new DaemonSpawn(i); for(int i = 0; i < SIZE; i++) System.out.println( "t[" + i + "].isdaemon() = " + t[i].isdaemon()); while(true) yield(); 15/03/2004 EPL 602 9
Παράδειγµα Daemon Threads class DaemonSpawn extends Thread { public DaemonSpawn(int i) { System.out.println("DaemonSpawn "+i+" started"); start(); public void run() { while(true) yield(); 15/03/2004 EPL 602 10
Παράδειγµα Daemon Threads public class Daemons { public static void main(string[] args) { Thread d = new Daemon(); System.out.println("d.isDaemon() = " + d.isdaemon()); // Allow the daemon threads to finish // their startup processes: BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Waiting for CR"); try { stdin.readline(); catch(ioexception e) { 15/03/2004 EPL 602 11
Παράδειγµα Daemon Threads Ένα Daemon thread κάνει το daemon flag = true και µετά δηµιουργεί ένα σύνολο από άλλα threads για να δείξει ότι και αυτά είναι daemons. Μετά µπαίνει σε άπειρο βρόχο καλώντας την µέθοδο yield( ) για να δώσει τον έλεγχο σε άλλες διεργασίες στο σύστηµα. Όταν η main( ) τελειώσει την εκτέλεσης της το πρόγραµµα τερµατίζει γιατί όλα τα threads που τρέχουν είναι daemons. Έτσι µπορείς να δει κανείς το αποτέλεσµα αποτο ξεκίνηµα όλων των daemon threads, Όλα τα threads περιµένουν από το System.in να διαβάσει τον χαρακτήρα CR (carriage return) για να τερµατίσουν. 15/03/2004 EPL 602 12
Θέµατα που προκύπτουν από Multithreading ιαµοιρασµός των πόρων: Ένα πρόγραµµα single-threaded είναι µια µοναδική οντότητα που κινείται στον χώρο του προβλήµατος και εκτελεί µια ενέργεια κάθε φορά. εν υπάρχει πρόβλήµα εφόσονδενµπορεί να έχεις δύο οντότητες οι οποίες προσπαθούν να χρησιµοποιήσουν ένα πόρο την ίδια στιγµή. Με το multithreading υπάρχει η πιθανότητα δύο ή περισσότερα threads να προσπαθήσουν να χρησιµοποιήσουν τον ίδιο πόρο ταυτόχρονα. Η σύγκρουση σε ένα πόρο πρέπει να αποφεύγεται διαφορετικά θα υπάρχουν δύο threads τα οποία προσπαθούν να χρησιµοποιήσουν τον ίδιο πόρο την ίδια στιγµή (π.χ. Η εκτύπωση στον ίδιο εκτυπωτή, ή ηαλλαγήτηςτιµής µιας µεταβλητής, κτλ.) 15/03/2004 EPL 602 13
Θέµατα που προκύπτουν από Multithreading Ένα θεµελιώδες πρόβληµα µε την χρήστη των threads είναι: Ποτέ δεν ξέρεις άν ένα thread τρέχει ή έχει κολλήσει. Χρειάζεται ένας τρόπος να αποτρέψεις δύο threads από την ταυτόχρονη χρήση ίδιου πόρου. (τουλάχιστον σε κρίσιµες περιόδους) Η αποτροπή αυτής της σύγκρουσης είναι θέµα κλειδώµατος lock on του πόρου όταν αυτός χρησιµοποιείται από κάποιο άλλο thread Το πρώτο thread το οποίο έχει πρόσβαση στο συγκεκριµένο πόρο, τον κλειδώνει locks it, καιέτσιτα άλλα threads περιµένουν µέχρι ο πόρος να ελευθερωθεί. 15/03/2004 EPL 602 14
Πως γίνεται στην Java ο συµµερισµός των πόρων Στην Java υπάρχει ένας αυτοµατοποιηµένος τρόπος αποφυγής των συγκρούσεων στην χρήση των πόρων: Η µνήµη είναιένααντικείµενο. Όπως κάνει µερικά µέρη του αντικειµένου private και η πρόσβαση σ αυτα γίνεται µόνο µέσω µεθόδων,µπορείς να αποφύγεις τις συγκρούσεις µε τηνδήλωσηµιας µεθόδου να είναι synchronized. Έτσι µόνο ένα thread κάθε φορά µπορείναέχειπρόσβασησε µέθοδο η οποία είνα synchronized για το συγκεκριµένο αντικείµενο. ήλωση µεθόδων synchronized : synchronized void f() { /*... */ synchronized void g(){ /*... */ 15/03/2004 EPL 602 15
Πως γίνεται στην Java ο συµµερισµός των πόρων Κάθε αντικείµενο περιέχει ένα lock (το οποίο ονοµάζεται και monitor) το οποίο είναι αυτόµατα κοµµάτι του αντικειµένου χωρίς να χρειάζεται να γράψεις ειδικό κώδικα. Όταν καλείς κάποια µέθοδο που είναι synchronized, το αντικείµενο κλειδώνει και κανένα άλλο thread δεν µπορεί να καλέσει την µέθοδο αυτή µέχρι locked and no other synchronized method of that object can be called until the first one finishes and releases the lock. Στο πιο πάνω παράδειγµα, αν η f( ) καλεστεί για ένα αντικείµενο η g( ) δεν µπορεί να καλεστεί για το ίδιο αντικείµενο µέχρι η f( ) να ολοκληρώσει την εργασία της. 15/03/2004 EPL 602 16
Πως γίνεται στην Java ο συµµερισµός των πόρων Υπάρχει ένα κλειδί το οποίο µοιράζονται όλες οι synchronized µέθοδοι ενός συγκεκριµένου αντικειµένου. Υπάρχει επίσης και ένα µοναδικό single lock per class, έτι ώστε οι synchronized και static µεθόδοι να µπορούν να κλειδώνουν η µια την άλλη από static data µέσα στο ίδιο το αντικείµενο Σηµείωση: Αν χρειάζεται να φυλάξεις πόρους από ταυτόχρονη πρόσβαση από πολλά threads, µπορεί να το κάνεις έχοντας τα σηµεία πρόσβασης στους πόρους αυτόύς σε µεθόδους που είναι synchronized. 15/03/2004 EPL 602 17
Synchronized Μπορείς αντί την λέξη κλειδί synchronized να βάλεις σε block synchronized τις γραµµές κώδικα που θέλεις Όλα τα synchronization είναι πρωτοβουλίες του προγραµµατιστή. 15/03/2004 EPL 602 18
Παράδειγµα public void run() { while (true) { synchronized(this) { t1.settext(integer.tostring(count1++)); t2.settext(integer.tostring(count2++)); try { sleep(500); catch (InterruptedException e){ 15/03/2004 EPL 602 19
Καταστάσεις ενός Thread Ένα thread µπορεί να βρίσκεται σε µια απο τις ακόλουθες καταστάσεις: New: Το αντικείµενο δηµιουργήθηκε αλλά δεν έχει καλεστεί η start και δεν έχει ξεκινήσει ακόµα να τρέχει. Runnable: Το thread µπορεί να τρέξει όταν υπάρχουν CPU cycles available. Dead: Μετά την εκτέλεση της run( ) πεθαίνει. Είτε µετά το κάλεσµα τηςstop( ). Blocked: Τρέχει αλλά κάτι εµποδίζει την λειτουργία του. Μέχριναγίνεικαιπάλιrunnable δεν µπορεί να κάνει οποιεσδήποτε εργασίες 15/03/2004 EPL 602 20
Blocked Thread Λόγοι που µπορεί ένα thread να γίνει block: Όταν καλεστεί η sleep(milliseconds). Όταν καλεστεί η suspend( ). Θα ξαναγίνει runnable όταν κάποιος καλέσει την resume( ). Όταν καλεστεί η wait( ). Θα ξαναγίνει runnable ξανά όταν καλεστεί η notify( ) ή notifyall( ). Το thread περιµένει κάποιο IO να τελείωσει. Όταν το thread προσπαθεί να καλέσει µέθοδο η οποία είναι synchronized σε ένα αντικείµενο και η µέθοδος αυτή είναι κλειδοµένη 15/03/2004 EPL 602 21
Blocked Thread Καλώντας την µέθοδο yield( ) (a method of the Thread class) θα παρατήσει εθελοντικά το CPU έτσι ώστε κάποιο άλλο threads να το χρησιµοποιήσει. Το ίδιο θα συµβεί αν ο scheduler αποφασίσει ότι το thread έχει δεσµεύσει αρκετό χρόνο και περνά σε κάποιο άλλο thread. Αυτό δεν σηµαίνει ότι,ο scheduler δεν θα ξεκινήσει το thread αυτό ξανά. Όταν το thread γίνει blocked, υπάρχει λόγος για να µην συνεχίζει την λειτουργία του. 15/03/2004 EPL 602 22
Wait και Notify Με τις µεθόδους sleep( ) και suspend( ) δηλώνεις ότι δεν πρέπει να ξεκλειδωθούν Από την άλλη η µεθοδος wait( ) παραδίνει το κλειδί όταν καλεστεί, έτσι οι άλλες synchronized µέθοδοι στο thread µπορούν να καλεστούν ενώ είναι σε wait( ). Υπάρχουν δύο ήδη wait( ). Με παράµετρο milliseconds όπως και στην sleep( ) Χωρίς παραµέτρους 15/03/2004 EPL 602 23
Wait και Notify Μπορείς να έχεις wait( ) µέσα σε synchronized µέθοδο, ανεξάρτητα αν έχεις ή όχι thread µέσα στο συγκεκριµένη αντικείµενο. Κατ ακρίβεια ο µόνος χώρος που µπορείς να καλέσεις την wait( ) είναι µέσα από synchronized µέθοδο ή block. Αν καλέσεις την wait( ) ήτην notify( ) µέσα σε µέθοδο που δεν είναι synchronized, το πρόγραµµα θα µεταγλωττιστεί σωστά όµως θα πάρεις λάθος ενώ τρέχει το πρόγραµµα IllegalMonitorStateException with the somewhat non-intuitive message current thread not owner. 15/03/2004 EPL 602 24
Wait και Notify Μπορείς να καλέσεις τις wait( ) και notify( ) µόνο σε δικό σου πλαίσιο κλειδώµατος. ιαφορετικά θα πάρεις IllegalMonitorStateException. εν µπορείς να ξεγελάσεις την κλειδαριά κάποιου άλλου αντικειµένου αλλά µπορείς να ζητήσεις από το άλλο αντικείµενο να εκτελέσει διεργασία που επεξεργάζεται το κλείδωµα. Μια προσέγγιση είναι µέσο synchronized µεθόδους που καλούν την notify( ) για το ίδιο αντικείµενο. 15/03/2004 EPL 602 25
Too Many Threads Πρέπει να παρακολουθείς µην έχεις too many threads. Ο προγραµµατιστής Θα πρέπει να εφαρµόζει τεχνικές για να κάνει balance τον αριθµό τωνthreads στο πρόγραµµα του. Άν παρατηρήσετε µείωση της απόδοσης σε τέτοια προγράµµατα υπάρχουν θέµατα τα οποία θα πρέπει να εξεταστούν: Υπάρχουν αρκετά καλέσµατα σε µεθόδους sleep( ), yield( ), wait( )? Είναι τα καλέσµατα στην sleep( ) αρκετά χρονοβόρα? Τρέχεις αρκετά threads? Έχεις δοκιµάσει και άλλες πλατφόρµες JVMs? Θέµατασανκαιαυτάκαθιστούντονmultithreading προγραµµατισµό ΤΕΧΝΗ. 15/03/2004 EPL 602 26
Deadlock Επειδή τα threads µπορεί να γίνουν blocked και επειδή τα αντικείµενα έχουν synchronized µεθόδους που εµποδίζουν τα threads από την πρόσβαση σε αυτά µέχρι να λήξει το synchronization, είναι δυνατό κάποιο thread να παραµείνει να παραµένει κάποιο άλλο thread, το οποίο µπορεί να περιµένει κάποιο άλλο κτλ και µε αυτότον τρόπο θα δηµιουργηθεί deadlock. εν υπάρχει γλώσσα που να µπορεί να το αποτρέψει. 15/03/2004 EPL 602 27
Περίληψη Είναι κρίσιµο να γνωρίζεις πότε πρέπει να χρησιµοποιηθεί multithreading και πότε µπορείς να το αποφύγεις. Κύρια µειονεκτήµατα του multithreading είναι: Καθυστέρηση ενώ περιµένει για shared resources Επιπλέον φόρτος στο CPU, για τον διαχειρισµό τωνthreads Πολυπλοκότητα που δέν οφελεί, χαζό παράδειγµα να έχω ένα thread για κάθε αλλαγή σε ένα στοιχείο κάποιου πίνακα Παθολογίες που προκύπτουν απο starving, racing, and deadlock Πλεονέκτηµα τωνthreads τους: Αποτελούν το light στην εκτέλεση εναλλαγής περιβάλλοντος (context switches ) 15/03/2004 EPL 602 28