Προγραμματισμός

JavaBeans: ιδιότητες, συμβάντα και ασφάλεια νημάτων

Η Java είναι μια δυναμική γλώσσα που περιλαμβάνει εύχρηστες κατασκευές γλωσσών πολλαπλών νημάτων και τάξεις υποστήριξης. Πολλά προγράμματα Java βασίζονται στο multithreading για να εκμεταλλευτούν τον παραλληλισμό εσωτερικών εφαρμογών, να βελτιώσουν την απόδοση του δικτύου ή να επιταχύνουν την απόκριση των σχολίων από τους χρήστες. Οι περισσότεροι χρόνοι εκτέλεσης Java χρησιμοποιούν multithreading για την εφαρμογή της δυνατότητας συλλογής απορριμμάτων Java. Τέλος, το AWT βασίζεται επίσης σε ξεχωριστά νήματα για τη λειτουργία του. Εν ολίγοις, ακόμη και τα απλούστερα προγράμματα Java γεννιούνται σε ένα ενεργό περιβάλλον πολλαπλών νημάτων.

Τα φασόλια Java, επομένως, αναπτύσσονται επίσης σε ένα τόσο δυναμικό περιβάλλον πολλαπλών νημάτων και εδώ βρίσκεται ο κλασικός κίνδυνος αντιμετώπισης συνθήκες αγώνα. Οι συνθήκες αγώνα είναι σενάρια ροής προγράμματος που εξαρτώνται από το χρόνο και μπορούν να οδηγήσουν σε καταστροφή της κατάστασης (δεδομένα προγράμματος). Στην επόμενη ενότητα θα αναλύσω δύο τέτοια σενάρια. Κάθε φασόλι Java πρέπει να σχεδιαστεί με γνώμονα τις συνθήκες του αγώνα, έτσι ώστε ένα φασόλι να μπορεί να αντέχει την ταυτόχρονη χρήση από πολλά νήματα πελατών.

Ζητήματα πολλαπλών νημάτων με απλές ιδιότητες

Οι υλοποιήσεις Bean πρέπει να υποθέσουν ότι πολλαπλά νήματα έχουν πρόσβαση ή / και τροποποιούν την παρουσία ενός φασολιού ταυτόχρονα. Ως παράδειγμα ακατάλληλου εφαρμοζόμενου φασολιού (καθώς σχετίζεται με την ευαισθητοποίηση πολλαπλών νημάτων), εξετάστε το ακόλουθο φασόλι BrokenProperties και το σχετικό πρόγραμμα δοκιμών MTProperties:

BrokenProperties.java

εισαγωγή java.awt.Point;

// Demo Bean που δεν προστατεύει από τη χρήση πολλαπλών νημάτων.

δημόσια τάξη BrokenProperties επεκτείνει το σημείο {

// ------------------------------------------------ ------------------- // set () / get () για την ιδιότητα "Spot" // --------------- -------------------------------------------------- -

public void setSpot (Point point) {// «spot» setter this.x = point.x; αυτό.y = point.y;

} Δημόσιο σημείο getSpot () {// "spot" getter επιστρέψτε αυτό; }} // End of Bean / Class BrokenProperties

MTProperties.java

εισαγωγή java.awt.Point; εισαγωγικά βοηθητικά προγράμματα. *; εισαγωγή βοηθητικών προγραμμάτων. φασόλια. *;

δημόσια τάξη MTProperties επεκτείνει το νήμα {

προστατευμένο BrokenProperties myBean; // το φασόλι στόχου για να χτυπήσει ..

προστατευμένο int myID; // κάθε νήμα φέρει λίγο αναγνωριστικό

// ------------------------------------------------ ------------------- // main () σημείο εισόδου // ---------------------- --------------------------------------------- δημόσιο στατικό κενό ( Συμβολοσειρά [] args) {

Φασόλι BrokenProperties; Νήμα νημάτων

bean = (BrokenProperties) BeansKit.newBean ("BrokenProperties");

για (int i = 0; i <20; i ++) {// ξεκινήστε 20 νήματα σε bash bean thread = νέα MTProperties (bean, i); // τα νήματα έχουν πρόσβαση στο bean thread.start (); }} // ---------------------------------------------- --------------------- // Κατασκευαστής MTProperties // ----------------------- --------------------------------------------

δημόσια MTProperties (BrokenProperties bean, int id) {this.myBean = bean; // σημειώστε το φασόλι για να αντιμετωπίσετε αυτό. myID = id; // σημειώστε ποιοι είμαστε} // ----------------------------------------- -------------------------- // ο κύριος βρόχος νήματος: // κάνει για πάντα // δημιουργήστε νέο τυχαίο σημείο με x == y // πείτε στο bean να υιοθετήσει το Point ως τη νέα ιδιότητά του «spot» // ρωτήστε το bean ποια είναι η ιδιότητα «spot» που έχει τώρα ρυθμίσει να // ρίξει μια ταλάντευση εάν το spot x δεν ισούται με το σημείο y // --------- -------------------------------------------------- -------- δημόσια άκυρη εκτέλεση () {int someInt; Σημείο σημείου = νέο σημείο ();

ενώ (true) {someInt = (int) (Math.random () * 100); point.x = someInt; point.y = someInt; myBean.setSpot (σημείο);

point = myBean.getSpot (); αν (point.x! = point.y) {System.out.println ("Bean κατεστραμμένο! x =" + point.x + ", y =" + point.y); System.exit (10); } System.out.print ((char) ('A' + myID)); System.out.flush (); }}} // Τέλος της κατηγορίας MTP Properties

Σημείωση: Το πακέτο βοηθητικών προγραμμάτων που εισήχθη από MTP Properties περιέχει επαναχρησιμοποιήσιμες τάξεις και στατικές μεθόδους που αναπτύχθηκαν για το βιβλίο από τον συγγραφέα.

Οι παραπάνω λίστες πηγών κώδικα ορίζουν ένα φασόλι που ονομάζεται BrokenProperties και την τάξη MTP Properties, το οποίο χρησιμοποιείται για την άσκηση του φασολιού από 20 σπειρώματα. Ας ακολουθήσουμε MTP Properties' κύριος() σημείο εισόδου: Πρώτα δημιουργεί ένα φασόλι BrokenProperties, ακολουθούμενο από τη δημιουργία και την έναρξη 20 νημάτων. Τάξη MTP Properties εκτείνεται java.lang.Tread, λοιπόν, το μόνο που χρειάζεται να κάνουμε για να γυρίσουμε την τάξη MTP Properties σε ένα νήμα είναι να παρακάμψετε την τάξη Νήμα'μικρό τρέξιμο() μέθοδος. Ο κατασκευαστής για τα νήματά μας παίρνει δύο ορίσματα: το αντικείμενο φασολιών με το οποίο θα επικοινωνήσει το νήμα και μια μοναδική ταυτοποίηση, η οποία επιτρέπει στα 20 νήματα να διαφοροποιούνται εύκολα στο χρόνο εκτέλεσης.

Το επιχειρηματικό τέλος αυτού του demo είναι δικό μας τρέξιμο() μέθοδος στην τάξη MTP Properties. Εδώ βγαίνουμε για πάντα, δημιουργώντας τυχαία νέα σημεία (x, y), αλλά με το ακόλουθο χαρακτηριστικό: η συντεταγμένη τους x ισούται πάντα με τη συντεταγμένη y. Αυτά τα τυχαία σημεία μεταφέρονται στα φασόλια setSpot () μέθοδο ρύθμισης και στη συνέχεια αμέσως ανάγνωση χρησιμοποιώντας το getSpot () μέθοδος λήψης. Θα περιμένατε την ανάγνωση σημείο ιδιότητα να είναι ίδια με το τυχαίο σημείο που δημιουργήθηκε πριν από μερικά χιλιοστά του δευτερολέπτου. Εδώ είναι ένα δείγμα εξόδου του προγράμματος όταν καλείται στη γραμμή εντολών:

ABBBBBBBBBBBBBBBBBBDJJJJJJJJJJJJJJJJJJJJEGHHHHHHHHHHHHHHHHHHSSSSSSSSSSSSSS IICCBBBBBBBBBBBBBBBBBKBDLJMBOPLQNRTPHHHHHHHHFFFFFFFFFFFFSSSSSSSSSSSSSSSSSS FFFFFFFFFFFFFFFAAAAAACCCCCCCKKKKKKKKKKKKKKKKKMMMMMMMMMMMMMMMMMMMMMMMMMMMDD JEOQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBTTTTTTTTTTTTTTTTLPPPPPPP PPPPGGHHHFFFFFFFFIIIIIIIIIIIIIISSSSSSSSSSSSSSSSSSSSACCCCCCCCCCCCCCCCCCCKMD QQQQQQNNNNNNNNNNNNNNNNRRRRRTRRHHHHHHHHHFFFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIII MMMJEEEEEEEEEEDDDDEEEEEEEEEOOOOOOOOOOOOOOOOOOOOOOOOOOOQNNNNNNNNBTLPLRGFFFF FFFFFFFFFIIAAAAAAAAAAAAAAAAASSSSSSSSSSSSSSSSSSKKKKKKKKKKKKKKKKCCCCCCCMMJAA AACBean καταστραφεί! x = 67, y = 13 OOOOOOOOOOOOOOOOOO 

Η έξοδος δείχνει τα 20 νήματα να τρέχουν παράλληλα (όσο ο άνθρωπος παρατηρεί). κάθε νήμα χρησιμοποιεί το αναγνωριστικό που έλαβε κατά την κατασκευή για να εκτυπώσει ένα από τα γράμματα ΕΝΑ προς την Τ, τα πρώτα 20 γράμματα του αλφαβήτου. Μόλις κάποιο νήμα ανακαλύψει ότι η ανάγνωση επιστρέφει σημείο Η ιδιότητα δεν συμμορφώνεται με το προγραμματισμένο χαρακτηριστικό του x = y, το νήμα εκτυπώνει ένα μήνυμα "Bean κατεστραμμένο" και σταματά το πείραμα.

Αυτό που βλέπετε είναι η κατάσταση που καταστρέφει την κατάσταση μιας κατάστασης αγώνα εντός των φασολιών setSpot () κώδικας. Εδώ είναι και πάλι αυτή η μέθοδος:

public void setSpot (Point point) {// «spot» setter this.x = point.x; αυτό.y = point.y; } 

Τι θα μπορούσε να πάει στραβά σε έναν τόσο απλό κώδικα; Φαντάζομαι νήμα Α κλήση setSpot () με ένα σημείο επιχείρησης ίσο με (67,67). Εάν τώρα επιβραδύνουμε το ρολόι του Σύμπαντος ώστε να δούμε την εικονική μηχανή Java (JVM) να εκτελεί κάθε δήλωση Java, μία κάθε φορά, μπορούμε να φανταστούμε νήμα Α εκτέλεση της δήλωσης αντιγραφής συντεταγμένων x (this.x = point.x;) και μετά, ξαφνικά, νήμα Α παγώνει από το λειτουργικό σύστημα και νήμα Γ έχει προγραμματιστεί να τρέξει για λίγο. Στην προηγούμενη κατάσταση λειτουργίας του, νήμα Γ μόλις είχε δημιουργήσει το δικό του νέο τυχαίο σημείο (13,13), που ονομάζεται setSpot () και στη συνέχεια παγώθηκε για να κάνει χώρο νήμα Μ, αμέσως μετά είχε ρυθμίσει τη συντεταγμένη x σε 13. Έτσι, συνεχίστηκε νήμα Γ συνεχίζει τώρα με την προγραμματισμένη λογική του: ρύθμιση y έως 13 και έλεγχος εάν η ιδιότητα spot είναι ίση (13, 13), αλλά διαπιστώνει ότι έχει αλλάξει μυστηριωδώς σε παράνομη κατάσταση (67, 13). η συντεταγμένη x είναι η μισή κατάσταση του τι νήμα Α έβγαινε σημείο στο, και η συντεταγμένη y είναι η μισή κατάσταση του τι νήμα Γείχε θέσεισημείο προς την. Το τελικό αποτέλεσμα είναι ότι το φασόλι BrokenProperties καταλήγει σε μια εσωτερική ασυνεπής κατάσταση: μια σπασμένη ιδιότητα.

Όποτε μη ατομικό δομή δεδομένων (δηλαδή, μια δομή που αποτελείται από περισσότερα από ένα μέρη) μπορεί να τροποποιηθεί από περισσότερα από ένα νήματα κάθε φορά, πρέπει να προστατεύσετε τη δομή χρησιμοποιώντας μια κλειδαριά. Στην Java, αυτό γίνεται χρησιμοποιώντας το συγχρονισμένος λέξη-κλειδί.

Προειδοποίηση: Σε αντίθεση με όλους τους άλλους τύπους Java, σημειώστε ότι η Java δεν το εγγυάται μακρύς και διπλό αντιμετωπίζονται ατομικά! Αυτό είναι επειδή μακρύς και διπλό απαιτούν 64 bit, το οποίο είναι διπλάσιο από το μήκος λέξεων των πιο σύγχρονων αρχιτεκτονικών CPU (32 bit). Τόσο η φόρτωση όσο και η αποθήκευση λέξεων ενός μηχανήματος είναι εγγενώς ατομικές λειτουργίες, αλλά η κίνηση οντοτήτων 64-bit απαιτεί δύο τέτοιες κινήσεις και αυτές δεν προστατεύονται από την Java για τον συνηθισμένο λόγο: απόδοση. (Ορισμένοι επεξεργαστές επιτρέπουν στο λεωφορείο του συστήματος να είναι κλειδωμένο για να εκτελούν ατομικές μεταφορές λέξεων-κλειδιών, αλλά αυτή η εγκατάσταση δεν είναι διαθέσιμη σε όλες τις CPU και, σε κάθε περίπτωση, θα ήταν απίστευτα ακριβό να εφαρμοστεί σε όλους μακρύς ή διπλό χειρισμοί!) Έτσι, ακόμη και όταν μια ιδιοκτησία αποτελείται από ένα μόνο μακρύς ή ένα διπλό, θα πρέπει να χρησιμοποιήσετε πλήρεις προφυλάξεις κλειδώματος για να προστατέψετε τα μακριά ή τα διπλά σας από ξαφνικά να καταστραφούν εντελώς.

ο συγχρονισμένος Η λέξη-κλειδί σηματοδοτεί ένα μπλοκ κώδικα ως ατομικό βήμα. Ο κωδικός δεν μπορεί να «διαιρεθεί», όπως όταν ένα άλλο νήμα διακόπτει τον κώδικα για να επανέλθει ενδεχομένως σε αυτό το μπλοκ (εξ ου και ο όρος κωδικός επανεισόδου; Όλος ο κώδικας Java θα πρέπει να επανεμφανιστεί). Η λύση για το φασόλι BrokenProperties μας είναι ασήμαντη: απλώς αντικαταστήστε την setSpot () μέθοδος με αυτό:

public void setSpot (Point point) {// «spot» setter συγχρονισμένο (αυτό) {this.x = point.x; αυτό.y = point.y; }} 

Ή εναλλακτικά με αυτό:

δημόσιο συγχρονισμένο άκυρο setSpot (Σημείο σημείου) {// «spot» setter this.x = point.x; αυτό.y = point.y; } 

Και οι δύο αντικαταστάσεις είναι απόλυτα ισοδύναμες, αν και προτιμώ το πρώτο στυλ γιατί δείχνει πιο καθαρά ποια είναι η ακριβής λειτουργία του συγχρονισμένος η λέξη-κλειδί είναι: ένα συγχρονισμένο μπλοκ είναι πάντα συνδέεται με ένα αντικείμενο που κλειδώνεται. Με κλειδωμένο Θέλω να πω ότι το JVM προσπαθεί πρώτα να αποκτήσει ένα κλείδωμα (δηλαδή, αποκλειστική πρόσβαση) στο αντικείμενο (δηλαδή να αποκτήσει αποκλειστική πρόσβαση σε αυτό) ή να περιμένει έως ότου το αντικείμενο ξεκλειδωθεί εάν έχει κλειδωθεί από άλλο νήμα. Η διαδικασία κλειδώματος εγγυάται ότι οποιοδήποτε αντικείμενο μπορεί να κλειδωθεί (ή να ανήκει) μόνο από ένα νήμα κάθε φορά.

Ετσι το συγχρονισμένος (αυτό) η σύνταξη αντικατοπτρίζει σαφώς τον εσωτερικό μηχανισμό: Το επιχείρημα μέσα στις παρενθέσεις είναι το αντικείμενο που πρέπει να κλειδωθεί (το τρέχον αντικείμενο) πριν από την εισαγωγή του μπλοκ κώδικα. Η εναλλακτική σύνταξη, όπου το συγχρονισμένος Η λέξη-κλειδί χρησιμοποιείται ως τροποποιητής σε μια υπογραφή μεθόδου, είναι απλά μια σύντομη έκδοση του πρώτου.

Προειδοποίηση: Όταν επισημαίνονται οι στατικές μέθοδοι συγχρονισμένος, δεν υπάρχει Αυτό αντικείμενο κλειδώματος μόνο οι μέθοδοι παρουσίας σχετίζονται με ένα τρέχον αντικείμενο. Έτσι, όταν οι μέθοδοι τάξης συγχρονίζονται, το java.lang.Class Αντικείμενο που αντιστοιχεί στην κλάση της μεθόδου χρησιμοποιείται αντ 'αυτού για να κλειδώσει. Αυτή η προσέγγιση έχει σοβαρές επιπτώσεις απόδοσης, επειδή μια συλλογή από παρουσίες τάξης μοιράζονται ένα συσχετισμένο Τάξη αντικείμενο; όποτε αυτό Τάξη το αντικείμενο κλειδώνεται, όλα τα αντικείμενα αυτής της κλάσης (είτε 3, 50 ή 1000!) δεν επιτρέπεται να επικαλούνται την ίδια στατική μέθοδο. Έχοντας αυτό υπόψη, πρέπει να σκεφτείτε δύο φορές πριν χρησιμοποιήσετε το συγχρονισμό με στατικές μεθόδους.

Στην πράξη, να θυμάστε πάντα τη ρητή συγχρονισμένη φόρμα, επειδή σας επιτρέπει να "ψεκάσετε" το μικρότερο δυνατό μπλοκ κώδικα μέσα σε μια μέθοδο. Η στενή μορφή "ψεκάζει" ολόκληρη τη μέθοδο, η οποία, για λόγους απόδοσης, είναι συχνά δεν τι θέλεις. Μόλις ένα νήμα έχει εισαγάγει ένα ατομικό μπλοκ κώδικα, κανένα άλλο νήμα που πρέπει να εκτελεστεί όποιος συγχρονισμένος κώδικας στο ίδιο αντικείμενο μπορεί να το κάνει.

Υπόδειξη: Όταν επιτυγχάνεται ένα κλείδωμα σε ένα αντικείμενο, τότε όλα συγχρονισμένος κώδικας για την κλάση αυτού του αντικειμένου θα γίνει ατομικός. Επομένως, εάν η τάξη σας περιέχει περισσότερες από μία δομές δεδομένων που πρέπει να αντιμετωπίζονται ατομικά, αλλά αυτές οι δομές δεδομένων είναι διαφορετικά ανεξάρτητος το ένα από το άλλο, τότε μπορεί να προκύψει ένα άλλο εμπόδιο απόδοσης Οι πελάτες που καλούν συγχρονισμένες μεθόδους που χειρίζονται μία εσωτερική δομή δεδομένων θα αποκλείσουν όλους τους άλλους πελάτες που καλούν τις άλλες μεθόδους που ασχολούνται με άλλες δομές ατομικών δεδομένων της τάξης σας. Σαφώς, θα πρέπει να αποφύγετε τέτοιες καταστάσεις χωρίζοντας την τάξη σε μικρότερες τάξεις που χειρίζονται μόνο μία δομή δεδομένων που πρέπει να αντιμετωπίζεται ατομικά κάθε φορά.

Το JVM εφαρμόζει τη δυνατότητα συγχρονισμού του δημιουργώντας ουρές νημάτων που περιμένουν να ξεκλειδωθεί ένα αντικείμενο. Παρόλο που αυτή η στρατηγική είναι εξαιρετική όσον αφορά την προστασία της συνοχής των σύνθετων δομών δεδομένων, μπορεί να οδηγήσει σε πολυσχιδή κυκλοφοριακή συμφόρηση όταν ένα λιγότερο από αποδοτικό τμήμα κώδικα επισημαίνεται ως συγχρονισμένος.

Επομένως, δώστε πάντα προσοχή στον αριθμό του κώδικα που συγχρονίζετε: πρέπει να είναι το ελάχιστο απαραίτητο. Για παράδειγμα, φανταστείτε μας setSpot () η μέθοδος αποτελούσε αρχικά:

public void setSpot (Point point) {// 'spot' setter log.println ("setSpot () που ζητήθηκε" + this.toString ()); this.x = point.x; αυτό.y = point.y; } 

παρόλο που το εκτύπωση δήλωση μπορεί λογικά ανήκουν στο setSpot () μέθοδος, δεν είναι μέρος της ακολουθίας δήλωσης που πρέπει να ομαδοποιηθεί σε ένα ατομικό σύνολο. Επομένως, σε αυτήν την περίπτωση, ο σωστός τρόπος χρήσης του συγχρονισμένος η λέξη-κλειδί θα έχει ως εξής:

public void setSpot (Point point) {// 'spot' setter log.println ("setSpot () που ζητήθηκε" + this.toString ()); συγχρονισμένο (αυτό) {this.x = point.x; αυτό.y = point.y; }} 

Ο «τεμπέλης» τρόπος και η προσέγγιση που πρέπει να αποφύγετε μοιάζει με αυτήν:

$config[zx-auto] not found$config[zx-overlay] not found