User Tools

Site Tools


java:synchronized_methods_blocks

Συγχρονισμένες μέθοδοι και συγχρονισμένα blocks

Η Java παρέχει δύο βασικούς τρόπους συγχρονισμού προκειμένου να αποφεύγονται τα προβλήματα συγχρονισμού που παρουσιάστηκαν προηγούμενα α) τις συγχρονισμένες μεθόδους και β) τα συγχρονισμένα blocks.

Η διαδικασίες συγχρονισμού που περιγράφονται παρακάτω βασίζονται σε έναν εσωτερικό μηχανισμό που διαθέτουν όλα τα αντικείμενα στην Java, το λεγόμενο intrinsic lock ή monitor lock ή monitor.

Όταν ένα νήμα θέλει να εκτελέσει τη συγχρονισμένη μέθοδο ενός αντικειμένου θα πρέπει να λάβει το monitor lock για το αντικείμενο αυτό. Αντίστοιχα, όταν το ίδιο νήμα θα βγει από την συγχρονισμένη μέθοδο του αντικειμένου θα ελευθερώσει το monitor lock για το αντικείμενο. Όταν ένα νήμα έχει το monitor lock για ένα αντικείμενο, κανένα άλλο νήμα δεν μπορεί να λάβει ταυτόχρονα αυτό το lock. Μόνο το υφιστάμενο αντικείμενο μπορεί να αφήσει το lock εξερχόμενο από την συγχρονισμένη μέθοδο ή το συγχρονισμένο block.

Συγχρονισμένες μέθοδοι

Προκειμένου να δημιουργήσετε μία συγχρονισμένη μέθοδο σε μία κλάση αρκεί να προσθέσετε το keyword synchronized κατά την δήλωση της μεθόδου, όπως παρακάτω

SynchronizedCounter.java
public class SynchronizedCounter {
  private int c = 0;
 
  public synchronized void increment() {
    c++;
  }
 
  public synchronized void decrement() {
    c--;
  }
 
  public synchronized int value() {
    return c;
  }
}

Αν έχουμε ένα αντικείμενο counter της παραπάνω κλάσης τότε ισχύουν τα παρακάτω

  • Δεν είναι δυνατόν η ίδια συγχρονισμένη μέθοδος ή διαφορετικές συγχρονισμένες μέθοδοι ενός αντικειμένου να κληθούν ταυτόχρονα από διαφορετικά νήματα. Ας υποθέσουμε ότι ένα νήμα εκτελεί μία από τις συγχρονισμένες μεθόδους ενός αντικειμένου. Όσα άλλα νήματα επιχειρούν να έχουν πρόσβαση σε συγχρονισμένες μεθόδους του ιδίου αντικειμένου, αναμένουν έως ότου το τρέχον νήμα να ολοκληρώσει την εκτέλεση της συγχρονισμένης μεθόδου την οποία εκτελεί και να ελευθερώσει το monitor lock για το αντικείμενο αυτό. Αυτό διασφαλίζει ότι η αλληλουχία των κλήσεων του παραδείγματος των νημάτων A,B που παρουσιάστηκε σε προηγούμενη σελίδα θα είναι η ενδεδειγμένη.

Συγχρονισμένα Βlocks

Σε αναλογία με τις συγχρονισμένες μεθόδους ορίζονται και τα συγχρονισμένα blocks με την διαφορά ότι τα συγχρονισμένα blocks οφείλουν να ορίσουν το αντικείμενο του οποίου λαμβάνουν το monitor lock, όπως παρακάτω

public class SynchronizedCounter {
  private int c = 0; 
  public void increment() { synchronized (this) {c++;} } 
  public void decrement() { synchronized (this) {c--;} } 
  public int value() { synchronized (this) {return c;} }
}

Στο παρακάτω παράδειγμα έχουμε δύο συγχρονισμένα blocκ μέσα σε δύο μη συγχρονισμένες μεθόδους. Η διαφορά είναι ότι η κλάση ορίζει δύο επιπλέον αντικείμενα μόνο και μόνο για να χρησιμοποιήσει τα monitor locks των αντικειμένων αυτών

SyncBlock.java
public class SyncBlock {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();
 
    public void inc1() {
        synchronized(lock1) {
            while(true) {
               for(long i=0; i<999999999L; i++)
                 ;
               System.out.println("Counter1: "+ ++c1);
            }
        }
    }
 
    public void inc2() {
        synchronized(lock2) {
            while(true) {
               for(long i=0; i<999999999L; i++)
                 ;
               System.out.println("Counter2: "+ ++c2);
            }
        }
    }
}

Στο παραπάνω παράδειγμα, οι μέθοδοι inc1 και inc2 περιέχουν δύο συγχρονισμένα blocks που κλειδώνουν από διαφορετικά αντικείμενα (lock1 και lock2). Αυτό πρακτικά σημαίνει ότι οι μέθοδοι inc1 και inc2 μπορούν να κληθούν ταυτόχρονα από δύο διαφορετικά νήματα. Ενδεικτικό είναι το παρακάτω πρόγραμμα:

SyncBlockThread.java
public class SyncBlockThread extends Thread {
  SyncBlock block;
  boolean counter1;
  public SyncBlockThread(SyncBlock block, boolean counter1) {
    this.block = block;
    this.counter1 = counter1;
  }
 
  public void run() {
    if(counter1)
      block.inc1();
    else
      block.inc2();
  }
 
  public static void main(String []args) {
    SyncBlock block = new SyncBlock();
    new SyncBlockThread(block, true).start();
    new SyncBlockThread(block, false).start();
  }
}

Αν και ένα νήμα δεν μπορεί να λάβει το monitor lock ενός αντικειμένου, όταν αυτό έχει καταληφθεί από άλλο αντικείμενο, το ίδιο νήμα μπορεί να λάβει το monitor lock του ιδίου αντικειμένου όσες φορές χρειαστεί αν υπάρχουν εμφωλευμένες (nested) κλήσεις συγχρονισμένων μεθόδων ή συγχρονισμένων blocks. Σε αυτές τις περιπτώσεις το monitor lock απελευθερώνεται μόνο όταν το νήμα βγει και από την αρχική συγχρονισμένη μέθοδο ή το αρχικό συγχρονισμένο block που μπήκε πριν από οποιαδήποτε άλλη κλήση συγχρονισμένης μεθόδου ή μπλοκ.

Δείτε το παρακάτω παράδειγμα εμφωλευμένων κλήσεων συγχρονισμένων μεθόδων

NestedSyncMethods.java
public class NestedSyncMethods {
  void synchronized outer() {
    inner();
  }
 
  void synchronized inner() {
   ...
  }
}

Αναφέρθηκε παραπάνω ότι μόνο ένα νήμα λαμβάνει το monitor lock ενός αντικειμένου προκειμένου να εκτελέσει το συγχρονισμένο block ή την συγχρονισμένη μέθοδο που συνδέεται με το αντικείμενο αυτό. Τα υπόλοιπα νήματα που πιθανόν θέλουν και εκείνα να προσπελάσουν τη συγκεκριμένη μέθοδο ή block βρίσκονται σε κατάσταση busy-wait, δηλαδή αναμένουν ενεργά μέχρι να ελευθερωθεί το monitor lock από τη μέθοδο ή το block στο οποίο αναμένουν.

java/synchronized_methods_blocks.txt · Last modified: 2018/03/08 12:03 (external edit)