User Tools

Site Tools


java:thread_signalling

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
java:thread_signalling [2015/04/19 15:28] – [Ξαφνικά ξυπνήματα (Spurious wake-ups)] gthanosjava:thread_signalling [2018/03/09 12:02] (current) – old revision restored (2018/03/09 13:23) gthanos
Line 1: Line 1:
 ====== Συγχρονισμός Νημάτων ====== ====== Συγχρονισμός Νημάτων ======
  
-Ο συγχρονισμός νημάτων έχει σαν στόχο να επιτρέπει σε διαφορετικά νήματα να διαβάζουν ή να γράφουν με ασφάλεια σε διαμοιραζόμενες μεταβλητές χωρίς να προκύπτουν ασάφειες ως προς τις τιμές των μεταβλητών αυτών λόγω ταυτόχρονης μεταβολής τους. Ο προσεκτικός αναγνώστης θα παρατηρήσει ότι το παραπάνω μπορούμε να το επιτύχουμε και με την χρήση συγχρονισμένων μπλοκ ή συγχρονισμένων μεθόδων. Το παραπάνω είναι εν μέρη σωστό, μόνο αν τα νήματα δύναται να εκτελούνται παράλληλα. Εάν τα νήματα δεν μπορούν να εκτελεστούν παράλληλα, αλλά το ένα νήμα θα πρέπει να περιμένει το άλλο να ολοκληρώσει τότε τα συγχρονισμένα block από μόνα τους δεν αποτελούν λύση. Σε αυτή την περίπτωση απαιτείται η έννοια της κλειδαριάς (lock). +Ο συγχρονισμός νημάτων έχει σαν στόχο να επιτρέπει σε διαφορετικά νήματα να διαβάζουν ή να γράφουν με ασφάλεια σε διαμοιραζόμενες μεταβλητές χωρίς να προκύπτουν ασάφειες ως προς τις τιμές των μεταβλητών αυτών λόγω ταυτόχρονης μεταβολής τους. Ο προσεκτικός αναγνώστης θα παρατηρήσει ότι την προστασία των διαμοιραζόμενων μεταβλητών μπορούμε να την διασφαλίσουμε με την χρήση συγχρονισμένων μπλοκ ή συγχρονισμένων μεθόδων. Το παραπάνω είναι σωστό, με την προϋπόθεση ότι τα νήματα επιθυμούμε να εκτελούνται παράλληλα. Εάν τα νήματα δεν πρέπει να εκτελεστούν παράλληλα, αλλά το ένα νήμα θα πρέπει να περιμένει το άλλο να ολοκληρώσει τότε τα συγχρονισμένα block από μόνα τους δεν αποτελούν λύση. Σε αυτή την περίπτωση απαιτείται η έννοια της κλειδαριάς (//lock//). 
  
-Ας υποθέσουμε ότι έχουμε δύο νήματα Α, Β και το νήμα Α θέλει να ειδοποιήσει το νήμα Β μόλις ολοκληρώσει την επεξεργασία των δεδομένων του, ώστε εκείνο να ξεκινήσει την επεξεργασία των δεδομένων που δημιουργήθηκαν. Δεν μας αρκεί δηλαδή τα νήματα Α, Β να εκτελούνται παράλληλα, αλλά θα θέλαμε το Β να ξεκινήσει την επεξεργασία μόλις το Α ολοκληρώσει.+Ας υποθέσουμε ότι έχουμε δύο νήματα Α, Β και το νήμα Α θέλει να ειδοποιήσει το νήμα Β μόλις ολοκληρώσει την επεξεργασία των δεδομένων του, ώστε εκείνο να ξεκινήσει την επεξεργασία των δεδομένων που δημιουργήθηκαν. Δεν μας αρκεί δηλαδή τα νήματα Α, Β να εκτελούνται παράλληλα, αλλά θα θέλαμε το νήμα Β να ξεκινήσει την επεξεργασία μόλις το νήμα Α ολοκληρώσει.
  
 ===== Συγχρονισμός μέσω διαμοιραζόμενων αντικειμένων και διαρκούς επανάληψης ===== ===== Συγχρονισμός μέσω διαμοιραζόμενων αντικειμένων και διαρκούς επανάληψης =====
  
-Ο πιο απλός τρόπος για να επικοινωνήσουν δύο νήματα μεταξύ τους είναι μέσω διαμοιραζόμενων αντικειμένων. Για το παραπάνω παράδειγμα, ας υποθέσουμε ότι το νήμα Α θέτει την boolean τιμή //hasDataToProcess// σε true μέσα από μία συγχρονισμένη μέθοδο και το νήμα Β διαβάζει την τιμή //hasDataToProcess// και πάλι μέσω μίας συγχρονισμένης μεθόδου+Ο πιο απλός τρόπος για να επικοινωνήσουν δύο νήματα μεταξύ τους είναι μέσω διαμοιραζόμενων αντικειμένων. Για το παραπάνω παράδειγμα, ας υποθέσουμε ότι το νήμα Α θέτει την boolean τιμή //locked// σε //true// μέσα από μία συγχρονισμένη μέθοδο και το νήμα Β διαβάζει την τιμή //locked// και πάλι μέσω μίας συγχρονισμένης μεθόδου
  
-<code java MySignal.java> +<code java MyLock.java> 
-public class MySignal{+public class MyLock{
  
-  protected boolean dataToProcess true;+  protected boolean locked false;
  
-  public synchronized boolean hasDataToProcess(){ +  public synchronized boolean isLocked(){ 
-    return dataToProcess;+    return locked;
   }   }
- +   
-  public synchronized void setHasDataToProcess(boolean hasData){ +  public synchronized void lock (){ 
-    this.dataToProcess hasData;  +    locked true;   
 +  } 
 +   
 +  public synchronized void unlock (){ 
 +    locked = false;  
   }   }
 } }
Line 31: Line 35:
 public class BusyWait implements Runnable { public class BusyWait implements Runnable {
  
-  protected MySignal signal;+  protected MyLock lock;
      
-  BusyWait(MySignal s) { +  BusyWait(MyLock l) { 
-    signal s;+    lock l;
   }   }
      
   public static void main(String args[]) {   public static void main(String args[]) {
-    MySignal sharedSignal = new MySignal(); +    MyLock sharedLock = new MyLock(); 
-    new Thread(new BusyWait(sharedSignal)).start(); +    new Thread(new BusyWait(sharedLock)).start(); 
-    new Thread(new BusyWait(sharedSignal)).start();+    new Thread(new BusyWait(sharedLock)).start();
   }   }
      
   public void run() {   public void run() {
     try {     try {
-      while(signal.hasDataToProcess() == false) {+      while(lock.isLocked() == true) { 
 +        Thread.sleep(300); 
 +        System.out.println(Thread.currentThread().getName()+" awaites locked for the lock.");
       }       }
-      signal.setHasDataToProcess(false);    +      // Uncomment the following line and see threads running concurrently. 
-      int i+      // Thread.sleep(10); 
-      for(i=0; i<10; i++) {+      lock.lock()   
 +      for(int i=0; i<10; i++) {
         Thread.sleep(100);         Thread.sleep(100);
         System.out.println(Thread.currentThread().getName()+" iteration: "+i);         System.out.println(Thread.currentThread().getName()+" iteration: "+i);
       }       }
-      signal.setHasDataToProcess(true);+      lock.unlock();
       System.out.println("Exiting!");       System.out.println("Exiting!");
     }     }
Line 60: Line 67:
     }     }
   }   }
- 
 } }
 </code> </code>
-Στο παράδειγμα αυτό το νήμα περιμένει, αλλά παράλληλα παραμένει και ενεργό (busy). Επίσης, θα παρατηρήσετε ότι με σημαντική πιθανότητα (αλλά όχι σίγουρα) τα δύο νήματα θα εκκινήσουν ταυτόχρονα και ταυτόχρονα διαβάσουν την τιμή **signal.hasDataToProcess() -> -1**  και θα αρχίσουν να εκτελούνται παράλληλα πράγμα που δεν το θέλουμε.+ 
 +Στο παράδειγμα αυτό το νήμα περιμένει, αλλά παράλληλα παραμένει και ενεργό (busy). Επίσης, θα παρατηρήσετε ότι με σημαντική πιθανότητα (αλλά όχι βεβαιότητα) τα δύο νήματα θα εκκινήσουν ταυτόχρονα και ταυτόχρονα διαβάσουν την τιμή **lock.isLocked() -> true**  και θα αρχίσουν να εκτελούνται παράλληλα πράγμα που δεν το θέλουμε. Δοκιμάστε να μεταγλωττίσετε και να εκτελέσετε μερικές φορές για να διαπιστώσετε την ταυτόχρονη εκτέλεση των νημάτων.
  
 ===== Συγχρονισμός με χρήση wait(), notify(), notifyAll() ===== ===== Συγχρονισμός με χρήση wait(), notify(), notifyAll() =====
Line 69: Line 76:
 Το παραπάνω σχήμα δεν είναι αποτελεσματικό ως προς τον συγχρονισμό και επίσης δαπανά πολλά resources καθώς το νήμα **Β** παραμένει ενεργό περιμένοντας. Θα ήταν πιο αποδοτικό αν αντί να παραμένει ενεργό το νήμα **Β** //"κοιμόταν"// περιμένοντας το νήμα **Α** να ολοκληρώσει. Η Java διαθέτει ένα μηχανισμό προκειμένου να επιτρέψει σε νήματα να κοιμηθούν περιμένοντας τα νήματα που εκτελούνται να ολοκληρώσουν το έργο τους. Συγκεκριμένα διαθέτει τρεις μεθόδους που επιτρέπουν το συγκεκριμένο συγχρονισμό [[https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--|wait()]], [[https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--|notify()]], [[https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notifyAll--|notifyAll()]]. Το παραπάνω σχήμα δεν είναι αποτελεσματικό ως προς τον συγχρονισμό και επίσης δαπανά πολλά resources καθώς το νήμα **Β** παραμένει ενεργό περιμένοντας. Θα ήταν πιο αποδοτικό αν αντί να παραμένει ενεργό το νήμα **Β** //"κοιμόταν"// περιμένοντας το νήμα **Α** να ολοκληρώσει. Η Java διαθέτει ένα μηχανισμό προκειμένου να επιτρέψει σε νήματα να κοιμηθούν περιμένοντας τα νήματα που εκτελούνται να ολοκληρώσουν το έργο τους. Συγκεκριμένα διαθέτει τρεις μεθόδους που επιτρέπουν το συγκεκριμένο συγχρονισμό [[https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--|wait()]], [[https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--|notify()]], [[https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notifyAll--|notifyAll()]].
  
-Όταν ένα νήμα καλεί την μέθοδο //wait()// πάνω σε ένα αντικείμενο, τότε το νήμα κοιμάται έως ότου ένα άλλο νήμα καλέσει τη μέθοδο //notify()// πάνω στο ίδιο αντικείμενο. Τότε το νήμα που κοιμόταν ξυπνάει και συνεχίζει την εκτέλεση του. Το νήμα που καλεί τις //wait()// και //notify()// __θα πρέπει να λάβει το monitor lock για το συγκεκριμένο αντικείμενο__, δηλαδή τόσο η //wait()// όσο και η //notify()// __θα πρέπει να κληθούν μέσα σε μία συγχρονισμένη μέθοδο ή συγχρονισμένο μπλοκ__. Δείτε το παρακάτω παράδειγμα συγχρονισμού με χρήση των //wait()/notify()//. +Όταν ένα νήμα καλεί την μέθοδο //wait()// πάνω σε ένα αντικείμενο, τότε το νήμα κοιμάται έως ότου ένα άλλο νήμα καλέσει τη μέθοδο //notify()// πάνω στο ίδιο αντικείμενο. Τότε το νήμα που κοιμόταν ξυπνάει και συνεχίζει την εκτέλεση του. Το νήμα που καλεί τις //wait()// και //notify()// __θα πρέπει να έχει λάβει προηγουμένως το //monitor lock// για το συγκεκριμένο αντικείμενο__, δηλαδή τόσο η //wait()// όσο και η //notify()// __θα πρέπει να κληθούν μέσα σε μία συγχρονισμένη μέθοδο ή συγχρονισμένο μπλοκ__. Δείτε το παρακάτω παράδειγμα συγχρονισμού με χρήση των //wait()/notify()//.
- +
-<code java MonitorObject.java> +
-public class MonitorObject{ +
-+
-</code>+
  
 <code java MyWaitNotify.java> <code java MyWaitNotify.java>
 public class MyWaitNotify{ public class MyWaitNotify{
  
-  MonitorObject myMonitorObject = new MonitorObject(); +  Object myMonitorObject = new Object(); 
-  boolean wasSignalled = false;+  boolean wasSignalled = true;
  
   public void doWait(){   public void doWait(){
Line 104: Line 106:
  
 Η μέθοδος //notify()// ξυπνάει ένα νήμα που περιμένει στο συγκεκριμένο //monitor lock//. Όπως βλέπετε οι μέθοδοι //wait()// και //notify()// καλούνται μέσα σε ένα συγχρονισμένο block του οποίου το //monitor lock// αφορά το αντικείμενο από το οποίο καλούνται οι //wait()// και //notify()//. Το παραπάνω σχήμα είναι υποχρεωτικό, δηλαδή (ισχύουν και τα δύο παρακάτω): Η μέθοδος //notify()// ξυπνάει ένα νήμα που περιμένει στο συγκεκριμένο //monitor lock//. Όπως βλέπετε οι μέθοδοι //wait()// και //notify()// καλούνται μέσα σε ένα συγχρονισμένο block του οποίου το //monitor lock// αφορά το αντικείμενο από το οποίο καλούνται οι //wait()// και //notify()//. Το παραπάνω σχήμα είναι υποχρεωτικό, δηλαδή (ισχύουν και τα δύο παρακάτω):
-  - οι //wait()// και //notify()// θα πρέπει να κληθούν μέσα σε ένα συγχρονισμένο block ή μία συγχρονισμένη μέθοδο. Αν δεν γίνει αυτό τότε λαμβάνουμε ένα [[http://docs.oracle.com/javase/7/docs/api/java/lang/IllegalMonitorStateException.html|IllegalMonitorStateException]]+  - οι //wait()// και //notify()// θα πρέπει να κληθούν μέσα σε ένα συγχρονισμένο block ή μία συγχρονισμένη μέθοδο.  
-  - Κατά την κλήση των //wait()/notify()// δεν μπορούμε να αποκτήσουμε το monitor lock ενός διαφορετικού αντικειμένου από αυτό με το οποίο καλούμε τις wait()/notify(), δηλ δεν μπορούμε να κάνουμε κάτι σαν το παρακάτω+  - Κατά την κλήση των //wait()/notify()// μπορούμε να καλέσουμε τις wait()/notify() μόνο για το αντικείμενο του οποίου έχουμε αποκτήσει το //monitor lock//. Εάν δεν ισχύει αυτό τότε λαμβάνουμε ένα [[http://docs.oracle.com/javase/7/docs/api/java/lang/IllegalMonitorStateException.html|IllegalMonitorStateException]].
  
-<code java> +Εκτός από τη μέθοδο //notify()// που ξυπνάει ένα νήμα από όσα περιμένουν υπάρχει και η //notifyAll()// που ξυπνάει όλα τα νήματα που περιμένουν για το συγκεκριμένο //monitor lock//. Όταν περισσότερα του ενός νήματα περιμένουντο ποιο νήμα από όλα θα ξυπνήσει η μέθοδος //notify()// δεν ελέγχεται από εσάς. Είστε όμως βέβαιοι ότι θα ξυπνήσει ένα νήμα.
-  public void doWait(){ +
-    synchronized(myMonitorObject){ +
-      if(!wasSignalled){ +
-        try{ +
-          myMonitorObject2.wait(); +
-         } catch(InterruptedException e){...} +
-      } +
-      //clear signal and continue running. +
-      wasSignalled = false; +
-    } +
-  } +
-</code> +
- +
-Εκτός της //notify()// υπάρχει και η //notifyAll()// που ξυπνάει όλα τα νήματα που περιμένουν στο συγκεκριμένο //monitor lock// σε αντίθεση με την notify() που ξυπνάει μόνο ένα νήμα. Όταν περισσότερα του ενός νήματα περιμένουν το ποιο νήμα θα ξυπνήσει η μέθοδος //notify()// δεν ελέγχεται από εσάς. Είστε όμως σίγουροι ότι θα ξυπνήσει ένα νήμα.+
  
 <WRAP important 80% round center> <WRAP important 80% round center>
-Ο παραπάνω κώδικας δεν μπορεί να εξασφαλίσει τον συγχρονισμό αν αντικαταστήσουμε την notify() με την notifyAll(). Γιατί;+Ο παραπάνω κώδικας δεν μπορεί να εξασφαλίσει τον συγχρονισμό αν αντικαταστήσουμε την //notify()// με την //notifyAll()//. Γιατί;
 </WRAP> </WRAP>
  
-Πως όμως το παραπάνω σχήμα είναι δυνατόν; Το thread που περιμένει δεν κρατά το monitor lock που έχει λάβει για το αντικείμενο myMonitorObject, όσο αυτό παραμένει μέσα στο συγχρονισμένο block ή την συγχρονισμένη μέθοδο; Η απάντηση είναι αρνητική. Όσο το νήμα περιμένει ελευθερώνει το συγκεκριμένο monitor lock. Το παραπάνω επιτρέπει σε άλλα νήματα που δεν έχουν το lock να λάβουν το lock μπαίνοντας σε ένα συγχρονισμένο block ή συγχρονισμένη συνάρτηση+Ας δούμε τώρα πως υλοποιείται ο παραπάνω μηχανισμός συγχρονισμού. Όταν ένα νήμα περιμένει μέσω της //wait()// ελευθερώνει παράλληλα το //monitor lock// που έχει λάβει για το αντικείμενο //myMonitorObject//, Η απελευθέρωση του //monitor lock// επιτρέπει σε άλλα νήματα που δεν έχουν το //lock// να το λάβουν μπαίνοντας σε ένα συγχρονισμένο block ή συγχρονισμένη μέθοδο
  
 <WRAP tip 80% round center> <WRAP tip 80% round center>
-Στην πραγματικότητα αυτό που συμβαίνει είναι λίγο πιο πολύπλοκο από όσο περιγράφεται παραπάνω. Όταν ένα νήμα ξυπνήσει δεν μπορεί αμέσως να βγει από την μέθοδο //wait()//. Αντίθετα θα πρέπει να περιμένει μέχρι το νήμα που κάλεσε την //notify()// να βγει από το συγχρονισμένο block ή την συγχρονισμένη μέθοδο. Αυτό συμβαίνει διότι το νήμα που ξυπνάει θα πρέπει να λάβει το //lock// του αντικειμένου από το οποίο κλήθηκε η μέθοδος //wait()// καθώς αυτή καλείται πάντα μέσα σε ένα συγχρονισμένο block ή μία συγχρονισμένη μέθοδο. Σε αναλογία, αν πολλαπλά νήματα ξυπνήσουν μέσα από μία κλήση της //notifyAll()//, τότε αυτά τα νήματα δεν μπορούν να ξυπνήσουν όλα μαζί, αλλά ένα-ένα καθώς κάθε ένα νήμα που ξυπνάει θα πρέπει να λάβει το lock πάνω στο οποίο περιμένει πριν συνεχίσει την εκτέλεση του.+Στην πραγματικότητα αυτό που συμβαίνει είναι λίγο πιο πολύπλοκο από όσο περιγράφεται παραπάνω. Όταν ένα νήμα ξυπνήσει δεν μπορεί αμέσως να βγει από την μέθοδο //wait()//. Αντίθετα θα πρέπει να περιμένει μέχρι το νήμα που κάλεσε την //notify()// να βγει από το συγχρονισμένο block ή την συγχρονισμένη μέθοδο. Αυτό συμβαίνει διότι το νήμα που ξυπνάει θα πρέπει να λάβει το //lock// του αντικειμένου από το οποίο κλήθηκε η μέθοδος //wait()// καθώς αυτή καλείται πάντα μέσα σε ένα συγχρονισμένο block ή μία συγχρονισμένη μέθοδο.  
 + 
 +Σε αναλογία, αν πολλαπλά νήματα ξυπνήσουν μέσα από μία κλήση της //notifyAll()//, τότε αυτά τα νήματα δεν μπορούν να ξυπνήσουν όλα μαζί, αλλά ένα-ένα καθώς κάθε ένα νήμα που ξυπνάει θα πρέπει να λάβει το lock πάνω στο οποίο περιμένει πριν συνεχίσει την εκτέλεση του.
 </WRAP> </WRAP>
  
 ===== Ξαφνικά ξυπνήματα (Spurious wake-ups) ===== ===== Ξαφνικά ξυπνήματα (Spurious wake-ups) =====
  
-Υπάρχει η πιθανότητα ένα νήμα να ξυπνήσει ακόμη και δίχως να κληθεί η //notify()// ή η //notifyAll()//. Αν και οι περιπτώσεις αυτές δεν είναι συχνές δεν θα πρέπει να αποκλειστούν. Σε αυτή την περίπτωση αν το νήμα λάβει το **lock** θα ξυπνήσει και θα συνεχίσει την εκτέλεση του δίχως να εξετάσει αν έχει έρθει ένα σήμα ή όχι. Για το λόγο αυτό, καλό θα είναι ο έλεγχος που γίνεται παραπάνω (μέσα στην //if()//) να αντικατασταθεί με ένα while() loop ώστε να είμαστε σίγουροι ότι όσο δεν έχει έρθει κάποιο σήμα το νήμα που περιμένει δεν θα συνεχίσει την εκτέλεση του. Δείτε τον τελικό κώδικα παρακάτω+Υπάρχει η πιθανότητα ένα νήμα να ξυπνήσει ακόμη και δίχως να κληθεί η //notify()// ή η //notifyAll()//, εφόσον το //monitor lock// είναι ελεύθερο. Αν και οι περιπτώσεις αυτές δεν είναι συχνές δεν θα πρέπει να αποκλειστούν. Σε αυτή την περίπτωση αν το νήμα λάβει το **lock** θα ξυπνήσει και θα συνεχίσει την εκτέλεση του δίχως να εξετάσει αν έχει έρθει ένα σήμα ή όχι. Για το λόγο αυτό, καλό θα είναι ο έλεγχος που γίνεται παραπάνω (μέσα στην //if()//) να αντικατασταθεί με ένα //while() loop// ώστε να είμαστε σίγουροι ότι όσο δεν έχει αλλάξει η τιμή της μεταβλητής //wasSignalled// το νήμα που περιμένει δεν θα συνεχίσει την εκτέλεση του. Δείτε τον τελικό κώδικα παρακάτω
  
 <code java MyWaitNotify2.java> <code java MyWaitNotify2.java>
 public class MyWaitNotify2{ public class MyWaitNotify2{
  
-  MonitorObject myMonitorObject = new MonitorObject();+  Object myMonitorObject = new Object();
   boolean wasSignalled = false;   boolean wasSignalled = false;
  
Line 169: Line 159:
 </WRAP> </WRAP>
  
-Παρατηρήστε ότι η κλάση MyWaitNotify2 υλοποιεί ένα +Παρατηρήστε ότι η κλάση **MyWaitNotify2** υλοποιεί ένα μία απλή κλειδαριά, όπου μόνο ένα νήμα μπορεί να πάρει την κλειδαριά, ενώ όλα τα υπόλοιπα νήματα περιμένουν. Μόλις ολοκληρώσει την επεξεργασία ελευθερώνει την κλειδαριά και ενημερώνει ένα από τα νήματα που περιμένουν να ξεκινήσει. Η παραπάνω κλάση θα μπορούσε να γραφεί με την μορφή κλειδαριάς ως εξής:
  
 +<code java SimpleLock.java>
 +public class SimpleLock {
 +
 +  Object myMonitorObject = new Object();
 +  boolean wasSignalled = false;
 +
 +  public void lock(){
 +    synchronized(myMonitorObject){
 +      while(!wasSignalled){
 +        try{
 +          myMonitorObject.wait();
 +         } catch(InterruptedException e){...}
 +      }
 +      //clear signal and continue running.
 +      wasSignalled = false;
 +    }
 +  }
 +
 +  public void unlock(){
 +    synchronized(myMonitorObject){
 +      wasSignalled = true;
 +      myMonitorObject.notify();
 +    }
 +  }
 +}
 +</code>
  
 +|Προηγούμενο: [[:java:synchronized_methods_blocks| Συγχρονισμένες μέθοδοι και blocks ]] | [[:toc | Περιεχόμενα ]] | Επόμενο: [[java:deadlock | Αδιέξοδο στην χρήση των πόρων ]]|
  
java/thread_signalling.1429457285.txt.gz · Last modified: 2015/04/19 14:28 (external edit)