java:concurrency_intro

This is an old revision of the document!


A PCRE internal error occured. This might be caused by a faulty plugin

====== Ταυτόχρονος Προγραμματισμός ====== Στον ταυτόχρονο προγραμματισμό, υπάρχουν δύο βασικές μονάδες εκτέλεσης: τις διεργασίες (processes) και τα νήματα (threads). Στη γλώσσα προγραμματισμού Java, ο ταυτόχρονος προγραμματισμός επιτυγχάνεται μέσω της χρήσης νημάτων. Ένα υπολογιστικό σύστημα έχει συνήθως πολλές ενεργές διεργασίες και νήματα. Αυτό ισχύει ακόμη και σε συστήματα που έχουν μόνο έναν πυρήνα στο υλικό τους, και ως εκ τούτου μπορούν να εκτελέσουν μόνο ένα νήμα ή διεργασία σε κάθε χρονική στιγμή. Ο χρόνος επεξεργασίας μεταξύ των διεργασιών και των νημάτων μοιράζεται μέσω του χρονοπρογραμματιστή (scheduler) του λειτουργικού συστήματος. Μία διεργασία έχει αυτόνομο περιβάλλον εκτέλεσης, δηλ έχει την δική της εικονική μνήμη και τις δικές της μεταβλητές. Συχνά οι διεργασίες ταυτίζονται με τα προγράμματα ή τις εφαρμογές, πράγμα που δεν είναι πάντα αληθές καθώς ένα πρόγραμμα μπορεί να είναι αποτέλεσμα περισσότερων διεργασιών οι οποίες επικοινωνούν μεταξύ τους μέσω των διεπαφών επικοινωνίας μεταξύ διεργασιών (Interprocess Communication - IPC) που ορίζει το κάθε λειτουργικό σύστημα. ===== Νήματα ===== Τα νήματα αναφέρονται συχνά και ως lightweight processes. Τόσο οι διεργασίες όσο και τα νήματα παρέχουν ένα περιβάλλον εκτέλεσης, αλλά απαιτούν λιγότερους πόρους κατά την δημιουργία τους σε σχέση με μία διεργασία. Τα νήματα ζουν μέσα στις διεργασίες (κάθε διεργασία έχει τουλάχιστον ένα thread) και μοιράζονται τους πόρους της διεργασίας, δηλαδή την μνήμη και τα αρχεία που έχει ανοίξει η διεργασία. Η πολυνηματική εκτέλεση αποτελεί βασικό χαρακτηριστικό της πλατφόρμας Java. Κάθε εφαρμογή έχει τουλάχιστον ένα νήμα (ή περισσότερα, αν μετρήσουμε και τα νήματα του JVM για κάθε διεργασία). Για τον προγραμματιστή αρχικά υπάρχει μόνο ένα νήμα το οποίο εκτελείται μέσα από τη μέθοδο //main//. Το νήμα αυτό έχει τη δυνατότητα να δημιουργήσει πρόσθετα νήματα, όπως θα δείξουμε στη συνέχεια. ===== Δημιουργία και εκτέλεση νημάτων ===== Υπάρχουν δύο παρεμφερείς τρόποι για την δημιουργία και εκτέλεση ενός νήματος. Και οι δύο παρατίθενται παρακάτω: <code java HelloRunnable.java> public class HelloRunnable implements Runnable { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new Thread(new HelloRunnable())).start(); } } </code> <code java HelloThread.java> public class HelloThread extends Thread { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new HelloThread()).start(); } } </code> Παρατηρείστε ότι και τα δύο παραπάνω παραδείγματα καλούν την μέθοδο [[http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#start()|Thread.start()]]. Από τα δύο το πρώτο είναι πιο γενικό, καθώς δεν απαιτείται η κλάση σας να είναι απόγονος της κλάσης [[http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html|Thread]]. ===== Σταματώντας την εκτέλεση ενός νήματος μέσω της μεθόδου sleep() ===== Η μέθοδος sleep δίνει την δυνατότητα σε ένα νήμα να σταματήσει την εκτέλεση του για ένα συγκεκριμένο χρονικό διάστημα. Κατά το διάστημα αυτό, άλλες διεργασίες ή threads της υφιστάμενης διεργασίες μπορούν να εκτελεστούν. Η χρονική διάρκεια κατά την οποία θα παύσει η εκτέλεση ενός thread μπορεί να μην είναι ακριβώς η χρονική διάρκεια που επιλέξαμε, καθώς ο χρονοπρογραμματισμός των threads εξαρτάται και από το λειτουργικό σύστημα. Δείτε το παρακάτω παράδειγμα κλήσης της μεθόδου sleep() <code java SleepMessages.java> public class SleepMessages { public static void main(String args[]) throws InterruptedException { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; for (int i=0; i<importantInfo.length; i++) { //Pause for 2 seconds Thread.sleep(2000); //Print a message System.out.println(importantInfo[i]); } } } </code> <WRAP center round 70% tip> Ένα νήμα μπορεί να καλέσει την μέθοδο sleep() για να κοιμηθεί το ίδιο και όχι για να κοιμήσει κάποιο άλλο νήμα. Ακόμη και εάν καλέσει την sleep() για να κοιμήσει ένα άλλο νήμα αυτό που θα γίνει είναι να κοιμηθεί το νήμα που την κάλεσε. </WRAP> ===== Επανενεργοποίηση ενός νήματος μέσω της μεθόδου interrupt() ===== Μία διακοπή (interrupt) αποτελεί ένδειξη προς την διεργασία να σταματήσει να κάνει αυτό που κάνει και να εκκινήσει κάτι διαφορετικό ή να τερματίσει. Η java υποστηρίζει την μέθοδο [[http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupt()|Thread.interrupt()]] για την αποστολή ενός σήματος διακοπής σε ένα νήμα. Εάν ένα νήμα έχει καλέσει την sleep() η κλήση της interrupt() για το νήμα αυτό, θα το αφυπνίσει και θα παραχθεί ένα [[http://docs.oracle.com/javase/7/docs/api/java/lang/InterruptedException.html|InterruptedException]] για το νήμα που κοιμόταν. Για τον λόγο αυτό, η κλήση της sleep() θα πρέπει να γίνεται πάντα μέσα σε ένα try/catch block της μορφής <code java> try { Thread.sleep(4000); } catch (InterruptedException e) { System.out.println(("We've been interrupted: no more messages."); return; } </code> Εάν ένα νήμα δεν καλεί κάποια μέθοδο που δημιουργεί [[http://docs.oracle.com/javase/7/docs/api/java/lang/InterruptedException.html|InterruptedException]] θα πρέπει περιοδικά να ελέγχει κατά πόσο υπάρχει κάποια σήμα διακοπής προς το συγκεκριμένο νήμα σε μία ανακύκλωση της μορφής <code java> for (int i = 0; i < inputs.length; i++) { heavyCalc(inputs[i]); if (Thread.interrupted()) { throw new InterruptedException(); } } </code> <WRAP center round 85% tip> Ο μηχανισμός διακοπής υλοποιείται χρησιμοποιώντας ένα εσωτερικό flag (boolean true/false) γνωστό ως interrupt status. H κλήση της Thread.interrupt() θέτει στην τιμή true αυτό το flag. Κάθε φορά που ένα νήμα ελέγχει αν έχει διακοπεί ή όχι (καλώντας τη στατική μέθοδο Thread.interrupted() ), το παραπάνω flag απενεργοποιείται λαμβάνοντας την τιμή false. Η μη στατική μέθοδος isInterrupted(), χρησιμοποιείται από ένα νήμα για να ενημερωθεί για την κατάσταση διακοπής του ιδίου νήματος ή ενός άλλου νήματος (εξαρτάται από το αντικείμενο που την καλεί), αλλά δεν αλλάζει την κατάσταση του flag διακοπής. </WRAP> ===== Περιμένοντας ένα νήμα να ολοκληρώσει μέσω της μεθόδου join() ===== Η μέθοδος [[http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()|join()]] επιτρέπει σε ένα νήμα να σταματήσει την εκτέλεση του έως ότου ένα άλλο νήμα να ολοκληρώσει την εκτέλεση του. Αν υποθέσουμε ότι ένα νήμα 't' εκτελείται παράλληλα με το τρέχον νήμα, εάν το τρέχον νήμα καλέσει το παρακάτω <code java> t.join(); </code> τότε η εκτέλεση του σταματάει έως ότου το νήμα 't' να τερματίσει την εκτέλεση του. Υπάρχουν οι εξής παραλλαγές της μεθόδου join * **t.join():** περίμενε μέχρι το νήμα t να τερματίσει * **t.join(long millis):** μέχρι το νήμα t να τερματίσει ή περίμενε για **millis** milliseconds. * **t.join(long millis, int nanos):** μέχρι το νήμα t να τερματίσει ή περίμενε για **millis+nanos/1000000** milliseconds. Όπως και η μέθοδος sleep() η μέθοδος join() δύναται να διακοπεί από ένα [[http://docs.oracle.com/javase/7/docs/api/java/lang/InterruptedException.html|InterruptedException()]]. ===== Εφαρμόζοντας τις μεθόδους sleep()/interrupt()/join() σε ένα παράδειγμα ===== Δείτε το παρακάτω παράδειγμα εκτέλεσης δύο νημάτων, το οποίο χρησιμοποιεί τις μεθόδους sleep(), interrupt() και join() <code java MessageLoops.java> public class MessageLoop implements Runnable { public void run() { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; try { for (int i = 0; i < importantInfo.length; i++) { // Pause for 4 seconds Thread.sleep(4000); // Print a message SimpleThreads.threadMessage(importantInfo[i]); } } catch (InterruptedException e) { SimpleThreads.threadMessage("I wasn't done!"); } } } </code> <code java SimpleThreads.java> public class SimpleThreads { // Display a message, preceded by // the name of the current thread public static void threadMessage(String message) { String threadName = Thread.currentThread().getName(); System.out.format("%s: %s%n", threadName, message); } public static void main(String args[]) throws InterruptedException { // Delay, in milliseconds before // we interrupt MessageLoop // thread (default one hour). long patience = 1000 * 60 * 60; // If command line argument // present, gives patience // in seconds. if (args.length > 0) { try { patience = Long.parseLong(args[0]) * 1000; } catch (NumberFormatException e) { System.err.println("Argument must be an integer."); System.exit(1); } } threadMessage("Starting MessageLoop thread"); long startTime = System.currentTimeMillis(); Thread t = new Thread(new MessageLoop()); t.start(); threadMessage("Waiting for MessageLoop thread to finish"); // loop until MessageLoop // thread exits while (t.isAlive()) { threadMessage("Still waiting..."); // Wait maximum of 1 second // for MessageLoop thread // to finish. t.join(1000); if (((System.currentTimeMillis() - startTime) > patience) && t.isAlive()) { threadMessage("Tired of waiting!"); t.interrupt(); // Shouldn't be long now // -- wait indefinitely t.join(); } } threadMessage("Finally!"); } }</code>

java/concurrency_intro.1427205670.txt.gz · Last modified: 2016/02/26 11:15 (external edit)