java synchronized what is thread synchronization java
Αυτό το σεμινάριο εξηγεί το συγχρονισμό νημάτων στην Java μαζί με συναφείς έννοιες όπως Java Lock, Race Condition, Mutex, Java Volatile & Deadlock στην Java:
Σε ένα περιβάλλον πολλαπλών νημάτων, όπου εμπλέκονται πολλά νήματα, είναι βέβαιο ότι θα υπάρξουν συγκρούσεις όταν περισσότερα από ένα νήματα προσπαθούν να λάβουν τον ίδιο πόρο ταυτόχρονα. Αυτές οι συγκρούσεις οδηγούν σε «κατάσταση αγώνα» και έτσι το πρόγραμμα παράγει απρόσμενα αποτελέσματα.
Για παράδειγμα, ένα μεμονωμένο αρχείο ενημερώνεται από δύο νήματα. Εάν ένα νήμα T1 βρίσκεται στη διαδικασία ενημέρωσης αυτού του αρχείου, πείτε κάποια μεταβλητή. Τώρα, ενώ αυτή η ενημέρωση από το T1 είναι ακόμη σε εξέλιξη, ας πούμε ότι το δεύτερο νήμα T2 ενημερώνει επίσης την ίδια μεταβλητή. Με αυτόν τον τρόπο η μεταβλητή θα δώσει λανθασμένα αποτελέσματα.
=> Παρακολουθήστε την πλήρη σειρά εκπαίδευσης Java εδώ.
Όταν εμπλέκονται πολλά νήματα, πρέπει να διαχειριζόμαστε αυτά τα νήματα με τέτοιο τρόπο ώστε ένας πόρος να μπορεί να έχει πρόσβαση σε ένα νήμα κάθε φορά. Στο παραπάνω παράδειγμα, η διαχείριση του αρχείου και από τα δύο νήματα πρέπει να γίνεται με τέτοιο τρόπο ώστε το T2 να μην μπορεί να έχει πρόσβαση στο αρχείο έως ότου ολοκληρωθεί η πρόσβαση στο T1.
Αυτό γίνεται στην Java χρησιμοποιώντας « Συγχρονισμός νημάτων '.
Τι θα μάθετε:
- Συγχρονισμός νημάτων σε Java
- Multi-threading χωρίς συγχρονισμό
- Multi-threading με συγχρονισμό
- συμπέρασμα
Συγχρονισμός νημάτων σε Java
Καθώς η Java είναι μια γλώσσα πολλαπλών νημάτων, ο συγχρονισμός νημάτων έχει μεγάλη σημασία στην Java καθώς πολλά νήματα εκτελούνται παράλληλα σε μια εφαρμογή.
Χρησιμοποιούμε λέξεις-κλειδιά 'Συγχρονισμένος' και 'πτητικός' για να επιτύχετε το συγχρονισμό στην Java
Χρειαζόμαστε συγχρονισμό όταν το κοινόχρηστο αντικείμενο ή πόρος είναι μεταβλητός. Εάν ο πόρος είναι αμετάβλητος, τότε τα νήματα θα διαβάσουν τον πόρο μόνο ταυτόχρονα ή μεμονωμένα.
Σε αυτήν την περίπτωση, δεν χρειάζεται να συγχρονίσουμε τον πόρο. Σε αυτήν την περίπτωση, η JVM το διασφαλίζει αυτό Ο συγχρονισμένος κώδικας Java εκτελείται από ένα νήμα κάθε φορά .
Τις περισσότερες φορές, η ταυτόχρονη πρόσβαση σε κοινόχρηστους πόρους στην Java ενδέχεται να προκαλέσει σφάλματα όπως 'ασυνέπεια μνήμης' και 'παρεμβολή νήματος'. Για να αποφύγουμε αυτά τα σφάλματα πρέπει να κάνουμε συγχρονισμό κοινόχρηστων πόρων έτσι ώστε η πρόσβαση σε αυτούς τους πόρους να είναι αμοιβαία αποκλειστική.
Χρησιμοποιούμε μια ιδέα που ονομάζεται Οθόνες για εφαρμογή συγχρονισμού. Μπορείτε να έχετε πρόσβαση σε μια οθόνη μόνο από ένα νήμα κάθε φορά. Όταν ένα νήμα πάρει το κλείδωμα, τότε, μπορούμε να πούμε ότι το νήμα έχει εισέλθει στην οθόνη.
Όταν γίνεται πρόσβαση σε μια οθόνη από ένα συγκεκριμένο νήμα, η οθόνη είναι κλειδωμένη και όλα τα άλλα νήματα που προσπαθούν να εισέλθουν στην οθόνη αναστέλλονται έως ότου το νήμα πρόσβασης τελειώσει και απελευθερώσει την κλειδαριά.
Στο μέλλον, θα συζητήσουμε λεπτομερώς το συγχρονισμό στην Java σε αυτό το σεμινάριο. Τώρα, ας συζητήσουμε μερικές βασικές έννοιες που σχετίζονται με το συγχρονισμό στην Java.
Κατάσταση αγώνων στην Ιάβα
Σε ένα περιβάλλον πολλαπλών νημάτων, όταν περισσότερα από ένα νήματα προσπαθούν να αποκτήσουν πρόσβαση σε έναν κοινόχρηστο πόρο για ταυτόχρονη εγγραφή, τότε πολλά νήματα συναγωνίζονται μεταξύ τους για να ολοκληρώσουν την πρόσβαση στον πόρο. Αυτό δημιουργεί «κατάσταση αγώνα».
Ένα πράγμα που πρέπει να λάβετε υπόψη είναι ότι δεν υπάρχει πρόβλημα εάν πολλά νήματα προσπαθούν να αποκτήσουν πρόσβαση σε έναν κοινόχρηστο πόρο μόνο για ανάγνωση. Το πρόβλημα προκύπτει όταν πολλά νήματα έχουν πρόσβαση στον ίδιο πόρο ταυτόχρονα.
Οι συνθήκες των αγώνων συμβαίνουν λόγω της έλλειψης κατάλληλου συγχρονισμού των νημάτων στο πρόγραμμα. Όταν συγχρονίζουμε σωστά τα νήματα έτσι ώστε κάθε φορά μόνο ένα νήμα θα έχει πρόσβαση στον πόρο και η κατάσταση του αγώνα παύει να υπάρχει.
Λοιπόν, πώς ανιχνεύουμε την κατάσταση του αγώνα;
Ο καλύτερος τρόπος για την ανίχνευση της κατάστασης του αγώνα είναι με τον έλεγχο κώδικα. Ως προγραμματιστής, πρέπει να αναθεωρήσουμε τον κώδικα διεξοδικά για να ελέγξουμε τις πιθανές συνθήκες αγώνα που ενδέχεται να προκύψουν.
Κλειδαριές / οθόνες στην Ιάβα
Έχουμε ήδη αναφέρει ότι χρησιμοποιούμε οθόνες ή κλειδαριές για την εφαρμογή συγχρονισμού. Η οθόνη ή το κλείδωμα είναι εσωτερική οντότητα και σχετίζεται με κάθε αντικείμενο. Έτσι, όποτε ένα νήμα χρειάζεται να έχει πρόσβαση στο αντικείμενο, πρέπει πρώτα να αποκτήσει το κλείδωμα ή την οθόνη του αντικειμένου του, να δουλέψει πάνω στο αντικείμενο και μετά να απελευθερώσει το κλείδωμα.
Οι κλειδαριές στην Java θα φαίνονται όπως φαίνεται παρακάτω:
public class Lock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while(isLocked) { wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } }
Όπως φαίνεται παραπάνω, έχουμε μια μέθοδο κλειδώματος () που κλειδώνει την παρουσία. Όλα τα νήματα που καλούν τη μέθοδο κλειδώματος () θα μπλοκαριστούν έως ότου τα σύνολα ξεμπλοκαρίσματος () να κλειδωθούν και να ειδοποιήσουν όλα τα νήματα που περιμένουν.
Μερικοί δείκτες που πρέπει να θυμάστε για τις κλειδαριές:
- Στην Java, κάθε αντικείμενο έχει κλειδαριά ή οθόνη. Αυτή η κλειδαριά μπορεί να προσεγγιστεί από ένα νήμα.
- Κάθε φορά μόνο ένα νήμα μπορεί να αποκτήσει αυτήν την οθόνη ή να κλειδώσει.
- Η γλώσσα προγραμματισμού Java παρέχει μια λέξη-κλειδί Synchronized ’που μας επιτρέπει να συγχρονίζουμε τα νήματα κάνοντας ένα μπλοκ ή μια μέθοδο ως συγχρονισμένη.
- Οι κοινόχρηστοι πόροι στους οποίους πρέπει να έχουν πρόσβαση τα νήματα διατηρούνται σε αυτό το συγχρονισμένο μπλοκ / μέθοδο.
Mutexes στην Ιάβα
Έχουμε ήδη συζητήσει ότι σε ένα περιβάλλον πολλαπλών νημάτων, ενδέχεται να προκύψουν συνθήκες αγώνα όταν περισσότερα από ένα νήματα προσπαθούν να αποκτήσουν πρόσβαση στους κοινόχρηστους πόρους ταυτόχρονα και οι συνθήκες αγώνα οδηγούν σε απροσδόκητη έξοδο.
Το μέρος του προγράμματος που προσπαθεί να αποκτήσει πρόσβαση στον κοινόχρηστο πόρο ονομάζεται «Κρίσιμη ενότητα» . Για να αποφευχθεί η εμφάνιση συνθηκών αγώνα, υπάρχει ανάγκη συγχρονισμού της πρόσβασης στην κρίσιμη ενότητα. Συγχρονίζοντας αυτήν την κρίσιμη ενότητα, διασφαλίζουμε ότι μόνο ένα νήμα μπορεί να έχει πρόσβαση στην κρίσιμη ενότητα κάθε φορά.
Ο απλούστερος τύπος συγχρονιστή είναι το 'mutex'. Το Mutex διασφαλίζει ότι σε κάθε δεδομένη περίπτωση, μόνο ένα νήμα μπορεί να εκτελέσει την κρίσιμη ενότητα.
Το mutex είναι παρόμοιο με την έννοια των οθονών ή κλειδαριών που συζητήσαμε παραπάνω. Εάν ένα νήμα πρέπει να αποκτήσει πρόσβαση σε μια κρίσιμη ενότητα, τότε πρέπει να αποκτήσει το mutex. Μόλις αποκτήσετε το mutex, το νήμα θα έχει πρόσβαση στον κρίσιμο κωδικό ενότητας και, όταν ολοκληρωθεί, θα απελευθερώσει το mutex.
Τα άλλα νήματα που περιμένουν να έχουν πρόσβαση στην κρίσιμη ενότητα θα αποκλειστούν εν τω μεταξύ. Μόλις το νήμα που κρατά το mutex το απελευθερώνει, ένα άλλο νήμα θα εισέλθει στην κρίσιμη ενότητα.
δωρεάν μετατροπέας youtube to wav
Υπάρχουν διάφοροι τρόποι με τους οποίους μπορούμε να εφαρμόσουμε ένα mutex στην Java.
- Χρήση συγχρονισμένης λέξης-κλειδιού
- Χρησιμοποιώντας το Semaphore
- Χρησιμοποιώντας το ReentrantLock
Σε αυτό το σεμινάριο, θα συζητήσουμε την πρώτη προσέγγιση, δηλαδή τον συγχρονισμό. Οι άλλες δύο προσεγγίσεις - Semaphore και ReentrantLock θα συζητηθούν στο επόμενο σεμινάριο όπου θα συζητήσουμε το java ταυτόχρονο πακέτο.
Συγχρονισμένη λέξη-κλειδί
Η Java παρέχει μια λέξη-κλειδί «Συγχρονισμένος» που μπορεί να χρησιμοποιηθεί σε ένα πρόγραμμα για την επισήμανση μιας κρίσιμης ενότητας. Η κρίσιμη ενότητα μπορεί να είναι ένα μπλοκ κώδικα ή μια πλήρης μέθοδο. Έτσι, μόνο ένα νήμα μπορεί να έχει πρόσβαση στην κρίσιμη ενότητα που επισημαίνεται από τη συγχρονισμένη λέξη-κλειδί.
Μπορούμε να γράψουμε τα ταυτόχρονα μέρη (μέρη που εκτελούνται ταυτόχρονα) για μια εφαρμογή χρησιμοποιώντας τη Συγχρονισμένη λέξη-κλειδί. Απαλλαγούμε επίσης από τις συνθήκες του αγώνα δημιουργώντας ένα μπλοκ κώδικα ή μια μέθοδο συγχρονισμένη.
Όταν επισημαίνουμε ένα μπλοκ ή μια μέθοδο συγχρονισμένη, προστατεύουμε τους κοινόχρηστους πόρους μέσα σε αυτές τις οντότητες από ταυτόχρονη πρόσβαση και κατά συνέπεια καταστροφή.
Τύποι συγχρονισμού
Υπάρχουν 2 τύποι συγχρονισμού όπως εξηγείται παρακάτω:
# 1) Συγχρονισμός διεργασιών
Η διαδικασία συγχρονισμού περιλαμβάνει πολλαπλές διεργασίες ή νήματα που εκτελούνται ταυτόχρονα. Τελικά φτάνουν σε μια κατάσταση όπου αυτές οι διαδικασίες ή τα νήματα δεσμεύονται για μια συγκεκριμένη ακολουθία ενεργειών.
# 2) Συγχρονισμός νημάτων
Στο Συγχρονισμό νημάτων, περισσότερα από ένα νήματα προσπαθούν να αποκτήσουν πρόσβαση σε κοινόχρηστο χώρο. Τα νήματα συγχρονίζονται με τέτοιο τρόπο ώστε ο κοινόχρηστος χώρος να έχει πρόσβαση μόνο από ένα νήμα κάθε φορά.
Ο Συγχρονισμός διεργασιών είναι εκτός του πεδίου αυτού του σεμιναρίου. Ως εκ τούτου, θα συζητήσουμε μόνο τον συγχρονισμό νημάτων εδώ.
Στην Java, μπορούμε να χρησιμοποιήσουμε τη συγχρονισμένη λέξη-κλειδί με:
- Ένα μπλοκ κώδικα
- Μια μέθοδος
Οι παραπάνω τύποι είναι οι αμοιβαία αποκλειστικοί τύποι συγχρονισμού νημάτων. Η αμοιβαία εξαίρεση εμποδίζει τα νήματα να έχουν πρόσβαση σε κοινόχρηστα δεδομένα ώστε να μην παρεμβαίνουν μεταξύ τους.
Ο άλλος τύπος συγχρονισμού νήματος είναι η 'InterThread επικοινωνία' που βασίζεται στη συνεργασία μεταξύ των νημάτων. Η επικοινωνία μεταξύ των νημάτων είναι εκτός του πεδίου αυτού του σεμιναρίου.
Πριν προχωρήσουμε στον συγχρονισμό των μπλοκ και των μεθόδων, ας εφαρμόσουμε ένα πρόγραμμα Java για να δείξουμε τη συμπεριφορά των νημάτων όταν δεν υπάρχει συγχρονισμός.
Multi-threading χωρίς συγχρονισμό
Το ακόλουθο πρόγραμμα Java έχει πολλά νήματα που δεν συγχρονίζονται.
class PrintCount { //method to print the thread counter public void printcounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run method for thread public void run() { PD.printcounter(); System.out.println('Thread ' + threadName + ' exiting.'); } //start method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args()) { PrintCount PD = new PrintCount(); //create two instances of thread class ThreadCounter T1 = new ThreadCounter( 'ThreadCounter_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'ThreadCounter_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }
Παραγωγή
Από την έξοδο, μπορούμε να δούμε ότι καθώς τα νήματα δεν είναι συγχρονισμένα, η έξοδος είναι ασυνεπής. Και τα δύο νήματα ξεκινούν και στη συνέχεια εμφανίζουν τον μετρητή το ένα μετά το άλλο. Και τα δύο νήματα εξέρχονται στο τέλος.
Από το δεδομένο πρόγραμμα, το πρώτο νήμα θα έπρεπε να είχε κλείσει μετά την εμφάνιση των τιμών μετρητή και στη συνέχεια το δεύτερο νήμα θα έπρεπε να είχε αρχίσει να εμφανίζει τις τιμές μετρητή.
Ας ξεκινήσουμε για συγχρονισμό και ξεκινήστε με το συγχρονισμό μπλοκ κώδικα.
Συγχρονισμένο μπλοκ κώδικα
Ένα συγχρονισμένο μπλοκ χρησιμοποιείται για το συγχρονισμό ενός μπλοκ κώδικα. Αυτό το μπλοκ αποτελείται συνήθως από μερικές γραμμές. Ένα συγχρονισμένο μπλοκ χρησιμοποιείται όταν δεν θέλουμε να συγχρονιστεί μια ολόκληρη μέθοδος.
Για παράδειγμα, έχουμε μια μέθοδο με 75 γραμμές κώδικα. Από αυτό απαιτούνται μόνο 10 γραμμές κώδικα για να εκτελούνται από ένα νήμα κάθε φορά. Σε αυτήν την περίπτωση, εάν κάνουμε ολόκληρη τη μέθοδο ως συγχρονισμένη, τότε θα επιβαρύνει το σύστημα. Σε τέτοιες περιπτώσεις, πηγαίνουμε για συγχρονισμένα μπλοκ.
Το πεδίο εφαρμογής της συγχρονισμένης μεθόδου είναι πάντα μικρότερο από αυτό της συγχρονισμένης μεθόδου. Μια συγχρονισμένη μέθοδος κλειδώνει ένα αντικείμενο ενός κοινόχρηστου πόρου που πρόκειται να χρησιμοποιηθεί από πολλά νήματα.
Η γενική σύνταξη ενός συγχρονισμένου μπλοκ είναι όπως φαίνεται παρακάτω:
synchronized (lock_object){ //synchronized code statements }
Εδώ 'lock_object' είναι μια έκφραση αναφοράς αντικειμένου στην οποία πρέπει να ληφθεί το κλείδωμα. Έτσι, όταν ένα νήμα θέλει να έχει πρόσβαση στις συγχρονισμένες δηλώσεις εντός του μπλοκ για εκτέλεση, τότε πρέπει να αποκτήσει το κλείδωμα στην οθόνη «lock_object».
Όπως έχει ήδη συζητηθεί, η συγχρονισμένη λέξη-κλειδί διασφαλίζει ότι μόνο ένα νήμα μπορεί να αποκτήσει ένα κλείδωμα κάθε φορά και όλα τα άλλα νήματα πρέπει να περιμένουν μέχρι το νήμα που κρατά το κλείδωμα να τελειώσει και να απελευθερώσει το κλείδωμα.
Σημείωση
εφαρμογή λήψης βίντεο youtube για υπολογιστή
- Ένα 'NullPointerException' ρίχνεται, εάν το κλείδωμα_ αντικείμενο που χρησιμοποιείται είναι Null.
- Εάν ένα νήμα κοιμάται ενώ κρατάτε την κλειδαριά, τότε η κλειδαριά δεν απελευθερώνεται. Τα άλλα νήματα δεν θα μπορούν να έχουν πρόσβαση στο κοινόχρηστο αντικείμενο κατά τη διάρκεια αυτού του χρόνου ύπνου.
Τώρα θα παρουσιάσουμε το παραπάνω παράδειγμα που είχε ήδη εφαρμοστεί με μικρές αλλαγές. Στο προηγούμενο πρόγραμμα, δεν συγχρονίσουμε τον κώδικα. Τώρα θα χρησιμοποιήσουμε το συγχρονισμένο μπλοκ και θα συγκρίνουμε την έξοδο.
Multi-threading με συγχρονισμό
Στο παρακάτω πρόγραμμα Java, χρησιμοποιούμε ένα συγχρονισμένο μπλοκ. Στη μέθοδο εκτέλεσης, συγχρονίζουμε τον κώδικα γραμμών που εκτυπώνουν τον μετρητή για κάθε νήμα.
class PrintCount { //print thread counter public void printCounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run () method for thread with synchronized block public void run() { synchronized(PD) { PD.printCounter(); } System.out.println('Thread ' + threadName + ' exiting.'); } //start () method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args()) { PrintCount PD = new PrintCount(); //create thread instances ThreadCounter T1 = new ThreadCounter( 'Thread_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'Thread_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }
Παραγωγή
Τώρα η έξοδος αυτού του προγράμματος χρησιμοποιώντας συγχρονισμένο μπλοκ είναι αρκετά συνεπής. Όπως ήταν αναμενόμενο, και τα δύο νήματα αρχίζουν να εκτελούνται. Το πρώτο νήμα τελείωσε την εμφάνιση των τιμών μετρητών και εξόδων. Στη συνέχεια, το δεύτερο νήμα εμφανίζει τις τιμές μετρητών και εξέρχεται.
Συγχρονισμένη μέθοδος
Ας συζητήσουμε τη συγχρονισμένη μέθοδο σε αυτήν την ενότητα. Νωρίτερα έχουμε δει ότι μπορούμε να δηλώσουμε ένα μικρό μπλοκ που αποτελείται από λιγότερες γραμμές κώδικα ως συγχρονισμένο μπλοκ. Εάν θέλουμε να συγχρονιστεί ολόκληρη η λειτουργία, τότε μπορούμε να δηλώσουμε μια μέθοδο ως συγχρονισμένη.
Όταν μια μέθοδος γίνεται συγχρονισμένη, τότε μόνο ένα νήμα θα μπορεί να κάνει μια κλήση μεθόδου κάθε φορά.
Η γενική σύνταξη για τη σύνταξη μιας συγχρονισμένης μεθόδου είναι:
synchronized method_name (parameters){ //synchronized code }
Ακριβώς όπως ένα συγχρονισμένο μπλοκ, στην περίπτωση μιας συγχρονισμένης μεθόδου, χρειαζόμαστε ένα lock_object που θα χρησιμοποιείται από νήματα που έχουν πρόσβαση στη συγχρονισμένη μέθοδο.
Για τη συγχρονισμένη μέθοδο, το αντικείμενο κλειδώματος μπορεί να είναι ένα από τα ακόλουθα:
- Εάν η συγχρονισμένη μέθοδος είναι στατική, τότε το αντικείμενο κλειδώματος δίνεται από το αντικείμενο «.class».
- Για μια μη στατική μέθοδο, το αντικείμενο κλειδώματος δίνεται από το τρέχον αντικείμενο, δηλαδή το αντικείμενο «αυτό».
Ένα ιδιαίτερο χαρακτηριστικό της συγχρονισμένης λέξης-κλειδιού είναι ότι επανεισέρχεται. Αυτό σημαίνει ότι μια συγχρονισμένη μέθοδος μπορεί να καλέσει μια άλλη συγχρονισμένη μέθοδο με το ίδιο κλείδωμα. Έτσι, ένα νήμα που κρατά την κλειδαριά μπορεί να αποκτήσει πρόσβαση σε μια άλλη συγχρονισμένη μέθοδο χωρίς να χρειάζεται να αποκτήσει διαφορετική κλειδαριά.
Η συγχρονισμένη μέθοδος παρουσιάζεται χρησιμοποιώντας το παρακάτω παράδειγμα.
class NumberClass { //synchronized method to print squares of numbers synchronized void printSquares(int n) throws InterruptedException { //iterate from 1 to given number and print the squares at each iteration for (int i = 1; i <= n; i++) { System.out.println(Thread.currentThread().getName() + ' :: '+ i*i); Thread.sleep(500); } } } public class Main { public static void main(String args()) { final NumberClass number = new NumberClass(); //create thread Runnable thread = new Runnable() { public void run() { try { number.printSquares(3); } catch (InterruptedException e) { e.printStackTrace(); } } }; //start thread instance new Thread(thread, 'Thread One').start(); new Thread(thread, 'Thread Two').start(); } }
Παραγωγή
Στο παραπάνω πρόγραμμα, χρησιμοποιήσαμε μια συγχρονισμένη μέθοδο για να εκτυπώσουμε τα τετράγωνα ενός αριθμού. Το ανώτερο όριο του αριθμού μεταφέρεται στη μέθοδο ως όρισμα. Στη συνέχεια, ξεκινώντας από το 1, τα τετράγωνα κάθε αριθμού εκτυπώνονται μέχρι να φτάσει το ανώτατο όριο.
Στην κύρια συνάρτηση, δημιουργείται η παρουσία νήματος. Κάθε παρουσία νήματος περνά έναν αριθμό για εκτύπωση τετραγώνων.
Όπως αναφέρθηκε παραπάνω, όταν μια μέθοδος συγχρονισμού είναι στατική, τότε το αντικείμενο κλειδώματος εμπλέκεται στην κλάση και όχι στο αντικείμενο. Αυτό σημαίνει ότι θα κλειδώσουμε στην τάξη και όχι στο αντικείμενο. Αυτό ονομάζεται στατικός συγχρονισμός.
Ένα άλλο παράδειγμα δίνεται παρακάτω.
class Table{ //synchronized static method to print squares of numbers synchronized static void printTable(int n){ for(int i=1;i<=10;i++){ System.out.print(n*i + ' '); try{ Thread.sleep(400); }catch(Exception e){} } System.out.println(); } } //thread class Thread_One class Thread_One extends Thread{ public void run(){ Table.printTable(2); } } //thread class Thread_Two class Thread_Two extends Thread{ public void run(){ Table.printTable(5); } } public class Main{ public static void main(String t()){ //create instances of Thread_One and Thread_Two Thread_One t1=new Thread_One (); Thread_Two t2=new Thread_Two (); //start each thread instance t1.start(); t2.start(); } }
Παραγωγή
Στο παραπάνω πρόγραμμα, εκτυπώνουμε πίνακες πολλαπλασιασμού αριθμών. Κάθε αριθμός του οποίου ο πίνακας πρόκειται να εκτυπωθεί είναι μια παρουσία νήματος διαφορετικής κλάσης νήματος. Έτσι εκτυπώνουμε πίνακες πολλαπλασιασμού των 2 & 5, οπότε έχουμε το thread_one και το thread_two δύο κατηγοριών για να εκτυπώσουμε τους πίνακες 2 και 5 αντίστοιχα.
Συνοψίζοντας, η συγχρονισμένη λέξη-κλειδί Java εκτελεί τις ακόλουθες λειτουργίες:
- Η συγχρονισμένη λέξη-κλειδί στην Java εγγυάται αμοιβαία αποκλειστική πρόσβαση σε κοινόχρηστους πόρους παρέχοντας έναν μηχανισμό κλειδώματος. Το κλείδωμα αποτρέπει επίσης τις συνθήκες του αγώνα.
- Χρησιμοποιώντας τη συγχρονισμένη λέξη-κλειδί, αποτρέπουμε ταυτόχρονα σφάλματα προγραμματισμού στον κώδικα.
- Όταν μια μέθοδος ή ένα μπλοκ δηλώνεται ως συγχρονισμένο, τότε ένα νήμα χρειάζεται ένα αποκλειστικό κλείδωμα για να εισέλθει στη συγχρονισμένη μέθοδο ή μπλοκ. Αφού εκτελέσει τις απαραίτητες ενέργειες, το νήμα απελευθερώνει την κλειδαριά και θα ξεπλύνει τη λειτουργία εγγραφής. Με αυτόν τον τρόπο θα εξαλειφθούν σφάλματα μνήμης που σχετίζονται με ασυνέπεια.
Πτητικό στην Ιάβα
Μια ευμετάβλητη λέξη-κλειδί στην Java χρησιμοποιείται για να κάνει τις τάξεις ασφαλείς στο νήμα. Χρησιμοποιούμε επίσης την ευμετάβλητη λέξη-κλειδί για να τροποποιήσουμε τη μεταβλητή τιμή από διαφορετικά νήματα. Μια πτητική λέξη-κλειδί μπορεί να χρησιμοποιηθεί για να δηλώσει μια μεταβλητή με πρωτόγονους τύπους καθώς και αντικείμενα.
Σε ορισμένες περιπτώσεις, μια ευμετάβλητη λέξη-κλειδί χρησιμοποιείται ως εναλλακτική λύση για τη συγχρονισμένη λέξη-κλειδί, αλλά σημειώστε ότι δεν αντικαθιστά τη συγχρονισμένη λέξη-κλειδί.
Όταν μια μεταβλητή δηλώνεται πτητική, η τιμή της δεν αποθηκεύεται ποτέ προσωρινά, αλλά διαβάζεται πάντα από την κύρια μνήμη. Μια μεταβλητή μεταβλητή εγγυάται την παραγγελία και την ορατότητα. Αν και μια μεταβλητή μπορεί να δηλωθεί ως πτητική, δεν μπορούμε να δηλώσουμε τάξεις ή μεθόδους ως ασταθείς.
Εξετάστε το ακόλουθο μπλοκ κώδικα:
class ABC{ static volatile int myvar =10; }
Στον παραπάνω κώδικα, η μεταβλητή myvar είναι στατική και πτητική. Μια στατική μεταβλητή μοιράζεται μεταξύ όλων των αντικειμένων της κλάσης. Η μεταβλητή μεταβλητή βρίσκεται πάντα στην κύρια μνήμη και δεν αποθηκεύεται ποτέ στην κρυφή μνήμη.
Ως εκ τούτου, θα υπάρχει μόνο ένα αντίγραφο του myvar στην κύρια μνήμη και όλες οι ενέργειες ανάγνωσης / εγγραφής θα γίνουν σε αυτήν τη μεταβλητή από την κύρια μνήμη. Εάν το myvar δεν είχε δηλωθεί ως πτητικό, τότε κάθε αντικείμενο νήματος θα είχε ένα διαφορετικό αντίγραφο που θα είχε ως αποτέλεσμα ασυνέπειες.
Μερικές από τις διαφορές μεταξύ των πτητικών και των συγχρονισμένων λέξεων-κλειδιών παρατίθενται παρακάτω.
Πτητική λέξη-κλειδί | Συγχρονισμένη λέξη-κλειδί |
---|---|
Η ευμετάβλητη λέξη-κλειδί χρησιμοποιείται μόνο με μεταβλητές. | Η συγχρονισμένη λέξη-κλειδί χρησιμοποιείται με μπλοκ κώδικα και μεθόδους. |
Μια ευμετάβλητη λέξη-κλειδί δεν μπορεί να αποκλείσει το νήμα για αναμονή. | Η συγχρονισμένη λέξη-κλειδί μπορεί να αποκλείσει το νήμα για αναμονή. |
Η απόδοση του νήματος βελτιώνεται με το Volatile. | Η απόδοση του νήματος υποβαθμίζεται κάπως με συγχρονισμένο. |
Οι πτητικές μεταβλητές βρίσκονται στην κύρια μνήμη. | Οι συγχρονισμένες κατασκευές δεν βρίσκονται στην κύρια μνήμη. |
Το πτητικό συγχρονίζει μία μεταβλητή μεταξύ της μνήμης νήματος και της κύριας μνήμης κάθε φορά. | Η συγχρονισμένη λέξη-κλειδί συγχρονίζει όλες τις μεταβλητές ταυτόχρονα. |
Αδιέξοδο στην Ιάβα
Έχουμε δει ότι μπορούμε να συγχρονίσουμε πολλά νήματα χρησιμοποιώντας συγχρονισμένη λέξη-κλειδί και να κάνουμε τα προγράμματα ασφαλή. Συγχρονίζοντας τα νήματα, διασφαλίζουμε ότι τα πολλαπλά νήματα εκτελούνται ταυτόχρονα σε ένα περιβάλλον πολλαπλών νημάτων.
Ωστόσο, μερικές φορές συμβαίνει μια κατάσταση στην οποία τα νήματα δεν μπορούν πλέον να λειτουργούν ταυτόχρονα. Αντ 'αυτού, περιμένουν ατελείωτα. Αυτό συμβαίνει όταν ένα νήμα περιμένει έναν πόρο και αυτός ο πόρος αποκλείεται από το δεύτερο νήμα.
Το δεύτερο νήμα, από την άλλη πλευρά, περιμένει τον πόρο που αποκλείεται από το πρώτο νήμα. Μια τέτοια κατάσταση δημιουργεί «αδιέξοδο» στην Java.
Το αδιέξοδο στην Java απεικονίζεται χρησιμοποιώντας την παρακάτω εικόνα.
Όπως μπορούμε να δούμε από το παραπάνω διάγραμμα, το νήμα Α έχει κλειδώσει τον πόρο r1 και περιμένει τον πόρο r2. Το νήμα Β, από την άλλη πλευρά, έχει μπλοκάρει τον πόρο r2 και περιμένει στο r1.
Έτσι, κανένα από τα νήματα δεν μπορεί να ολοκληρώσει την εκτέλεσή του, εκτός εάν καταλάβει τους εκκρεμείς πόρους. Αυτή η κατάσταση είχε ως αποτέλεσμα το αδιέξοδο όπου και τα δύο νήματα περιμένουν ατελείωτα τους πόρους.
Δίνεται παρακάτω ένα παράδειγμα αδιεξόδων στην Java.
public class Main { public static void main(String() args) { //define shared resources final String shared_res1 = 'Java tutorials'; final String shared_res2 = 'Multithreading'; // thread_one => locks shared_res1 then shared_res2 Thread thread_one = new Thread() { public void run() { synchronized (shared_res1) { System.out.println('Thread one: locked shared resource 1'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res2) { System.out.println('Thread one: locked shared resource 2'); } } } }; // thread_two=> locks shared_res2 then shared_res1 Thread thread_two = new Thread() { public void run() { synchronized (shared_res2) { System.out.println('Thread two: locked shared resource 2'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res1) { System.out.println('Thread two: locked shared resource 1'); } } } }; //start both the threads thread_one.start(); thread_two.start(); } }
Παραγωγή
Στο παραπάνω πρόγραμμα, έχουμε δύο κοινόχρηστους πόρους και δύο νήματα. Και τα δύο νήματα προσπαθούν να αποκτήσουν πρόσβαση στους κοινόχρηστους πόρους ένα προς ένα. Η έξοδος δείχνει και τα δύο νήματα να κλειδώνουν έναν πόρο το καθένα ενώ περιμένουν τα άλλα. Με αυτόν τον τρόπο δημιουργείται μια κατάσταση αδιεξόδου.
Παρόλο που δεν μπορούμε να σταματήσουμε την πλήρη εμφάνιση καταστάσεων αδιεξόδου, σίγουρα μπορούμε να τις αποφύγουμε κάνοντας κάποια βήματα.
Παρακάτω αναφέρονται τα μέσα με τα οποία μπορούμε να αποφύγουμε τα αδιέξοδα στην Java.
# 1) Αποφεύγοντας ένθετες κλειδαριές
Η ύπαρξη κλειδωμένων κλειδαριών είναι ο πιο σημαντικός λόγος για την ύπαρξη αδιεξόδων. Οι ένθετες κλειδαριές είναι οι κλειδαριές που δίνονται σε πολλά νήματα. Επομένως, πρέπει να αποφύγουμε να δώσουμε κλειδαριές σε περισσότερα από ένα νήματα.
# 2) Χρησιμοποιήστε το νήμα Join
Πρέπει να χρησιμοποιήσουμε το Thread.join με μέγιστο χρόνο, ώστε τα νήματα να μπορούν να χρησιμοποιούν τον μέγιστο χρόνο εκτέλεσης. Αυτό θα αποτρέψει το αδιέξοδο που συμβαίνει κυρίως καθώς ένα νήμα περιμένει συνεχώς τους άλλους.
# 3) Αποφύγετε την περιττή κλειδαριά
Πρέπει να κλειδώσουμε μόνο τον απαραίτητο κωδικό. Η ύπαρξη περιττών κλειδαριών για τον κώδικα μπορεί να οδηγήσει σε αδιέξοδα στο πρόγραμμα. Καθώς τα αδιέξοδα μπορούν να σπάσουν τον κώδικα και να εμποδίσουν τη ροή του προγράμματος, πρέπει να έχουμε την τάση να αποφεύγουμε τα αδιέξοδα στα προγράμματά μας.
Συχνές Ερωτήσεις
Q # 1) Τι είναι ο συγχρονισμός και γιατί είναι σημαντικός;
Απάντηση: Ο συγχρονισμός είναι η διαδικασία ελέγχου της πρόσβασης ενός κοινόχρηστου πόρου σε πολλά νήματα. Χωρίς συγχρονισμό, πολλά νήματα μπορούν να ενημερώσουν ή να αλλάξουν τον κοινόχρηστο πόρο ταυτόχρονα με συνέπεια ασυνέπειες.
Επομένως, πρέπει να διασφαλίσουμε ότι σε ένα περιβάλλον πολλαπλών νημάτων, τα νήματα συγχρονίζονται έτσι ώστε ο τρόπος με τον οποίο έχουν πρόσβαση στους κοινόχρηστους πόρους είναι αμοιβαία αποκλειστικός και συνεπής.
Q # 2) Τι είναι ο συγχρονισμός και ο μη συγχρονισμός στην Java;
Απάντηση: Ο συγχρονισμός σημαίνει ότι μια κατασκευή είναι ασφαλής για νήματα. Αυτό σημαίνει ότι πολλά νήματα δεν μπορούν να έχουν πρόσβαση στην κατασκευή (μπλοκ κώδικα, μέθοδος κ.λπ.) ταυτόχρονα.
Οι μη συγχρονισμένες κατασκευές δεν είναι ασφαλείς για νήματα. Πολλά νήματα μπορούν να έχουν πρόσβαση στις μη συγχρονισμένες μεθόδους ή μπλοκ ανά πάσα στιγμή. Μια δημοφιλής μη συγχρονισμένη κλάση στην Java είναι το StringBuilder.
Q # 3) Γιατί απαιτείται συγχρονισμός;
Απάντηση: Όταν οι διαδικασίες πρέπει να εκτελούνται ταυτόχρονα, χρειαζόμαστε συγχρονισμό. Αυτό συμβαίνει επειδή χρειαζόμαστε πόρους που μπορούν να μοιραστούν μεταξύ πολλών διαδικασιών.
Προκειμένου να αποφευχθούν συγκρούσεις μεταξύ διαδικασιών ή νημάτων για πρόσβαση σε κοινόχρηστους πόρους, πρέπει να συγχρονίσουμε αυτούς τους πόρους έτσι ώστε όλα τα νήματα να έχουν πρόσβαση σε πόρους και η εφαρμογή να λειτουργεί επίσης ομαλά.
Q # 4) Πώς αποκτάτε μια Συγχρονισμένη λίστα ArrayList;
Απάντηση: Μπορούμε να χρησιμοποιήσουμε τη μέθοδο Collections.synchronized list με το ArrayList ως επιχείρημα για τη μετατροπή του ArrayList σε μια συγχρονισμένη λίστα.
Q # 5) Συγχρονίζεται το HashMap;
σετ κεφαλής εικονικής πραγματικότητας συμβατό με το ps4
Απάντηση: Όχι, το HashMap δεν είναι συγχρονισμένο, αλλά το HashTable είναι συγχρονισμένο.
συμπέρασμα
Σε αυτό το σεμινάριο, συζητήσαμε λεπτομερώς τον Συγχρονισμό νημάτων. Μαζί με αυτό, μάθαμε επίσης για τις ασταθείς λέξεις-κλειδιά και τα αδιέξοδα στην Java. Ο συγχρονισμός αποτελείται από συγχρονισμό διεργασιών και νημάτων.
Σε ένα περιβάλλον πολλαπλών νημάτων, μας ενδιαφέρει περισσότερο ο συγχρονισμός των νημάτων. Εδώ έχουμε δει την προσέγγιση συγχρονισμένων λέξεων-κλειδιών του συγχρονισμού νήματος.
Το αδιέξοδο είναι μια κατάσταση όπου πολλά νήματα περιμένουν αδιάκοπα τους πόρους. Έχουμε δει το παράδειγμα των αδιεξόδων στην Java μαζί με τις μεθόδους για την αποφυγή αδιεξόδων στην Java.
=> Επισκεφθείτε εδώ για να μάθετε Java από το μηδέν.
Συνιστώμενη ανάγνωση
- Thread.Sleep () - Thread Sleep () Μέθοδος στην Java με παραδείγματα
- Νήματα Java με μεθόδους και κύκλο ζωής
- Βασικά Java: Java Syntax, Java Class και Core Java Concepts
- Multithreading In Java - Εκμάθηση με παραδείγματα
- Multithreading σε C ++ με παραδείγματα
- Εκπαιδευτικό πρόγραμμα JAVA για αρχάριους: 100+ πρακτικά εκπαιδευτικά βίντεο Java
- Java Components: Java Platform, JDK, JRE και Java Virtual Machine
- Εκμάθηση συμβολοσειράς Java | Μέθοδοι συμβολοσειράς Java με παραδείγματα