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

Σχεδιασμός για ασφάλεια νήματος

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

Τι είναι η ασφάλεια του νήματος;

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

Μία από τις πρώτες οδηγίες που πρότεινα σε αυτήν τη στήλη (βλ. "Σχεδιασμός αρχικοποίησης αντικειμένων") είναι ότι πρέπει να σχεδιάσετε τάξεις έτσι ώστε τα αντικείμενα να διατηρούν μια έγκυρη κατάσταση, από την αρχή της διάρκειας ζωής τους έως το τέλος. Εάν ακολουθήσετε αυτήν την συμβουλή και δημιουργήσετε αντικείμενα των οποίων οι μεταβλητές παρουσίας είναι όλες ιδιωτικές και των οποίων οι μέθοδοι πραγματοποιούν μόνο κατάλληλες μεταβάσεις κατάστασης σε αυτές τις μεταβλητές παρουσίας, είστε σε καλή κατάσταση σε ένα περιβάλλον με ένα νήμα. Αλλά μπορεί να αντιμετωπίσετε προβλήματα όταν έρχονται περισσότερα νήματα.

Πολλά νήματα μπορεί να προκαλέσουν προβλήματα στο αντικείμενο σας επειδή συχνά, ενώ μια μέθοδος βρίσκεται στη διαδικασία εκτέλεσης, η κατάσταση του αντικειμένου σας μπορεί να είναι προσωρινά άκυρη. Όταν μόνο ένα νήμα επικαλείται τις μεθόδους του αντικειμένου, μόνο μία μέθοδος κάθε φορά θα εκτελείται και κάθε μέθοδο θα επιτρέπεται να ολοκληρωθεί πριν από την κλήση μιας άλλης μεθόδου. Έτσι, σε ένα μονό νήμα περιβάλλον, σε κάθε μέθοδο θα δοθεί η ευκαιρία να βεβαιωθεί ότι οποιαδήποτε προσωρινά άκυρη κατάσταση έχει αλλάξει σε έγκυρη κατάσταση πριν επιστρέψει η μέθοδος.

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

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

Γιατί να ανησυχείτε για την ασφάλεια του νήματος;

Υπάρχουν δύο μεγάλοι λόγοι για τους οποίους πρέπει να σκεφτείτε την ασφάλεια των νημάτων όταν σχεδιάζετε μαθήματα και αντικείμενα στην Java:

  1. Η υποστήριξη για πολλά νήματα είναι ενσωματωμένη στη γλώσσα Java και το API

  2. Όλα τα νήματα μέσα σε μια εικονική μηχανή Java (JVM) μοιράζονται την ίδια περιοχή σωρού και μεθόδου

Επειδή το multithreading είναι ενσωματωμένο στην Java, είναι πιθανό οποιαδήποτε κλάση που σχεδιάζετε να μπορεί να χρησιμοποιηθεί ταυτόχρονα από πολλά νήματα. Δεν χρειάζεται (και δεν πρέπει) να κάνετε κάθε τάξη που σχεδιάζετε ασφαλή για νήματα, επειδή η ασφάλεια των νημάτων δεν έρχεται δωρεάν. Αλλά πρέπει τουλάχιστον νομίζω για την ασφάλεια των νημάτων κάθε φορά που σχεδιάζετε μια τάξη Java. Θα βρείτε μια συζήτηση σχετικά με το κόστος της ασφάλειας του νήματος και τις οδηγίες σχετικά με το πότε να κάνετε τις τάξεις ασφαλείς για νήματα αργότερα σε αυτό το άρθρο.

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

Δεν χρειάζεται να ανησυχείτε για την πρόσβαση πολλαπλών νημάτων σε τοπικές μεταβλητές, παραμέτρους μεθόδου και τιμές επιστροφής, επειδή αυτές οι μεταβλητές βρίσκονται στη στοίβα Java. Στο JVM, σε κάθε νήμα απονέμεται η δική του στοίβα Java. Κανένα νήμα δεν μπορεί να δει ή να χρησιμοποιήσει τοπικές μεταβλητές, τιμές επιστροφής ή παραμέτρους που ανήκουν σε άλλο νήμα.

Δεδομένης της δομής του JVM, οι τοπικές μεταβλητές, οι παράμετροι της μεθόδου και οι τιμές επιστροφής είναι εγγενώς "ασφαλείς για το νήμα". Ωστόσο, οι μεταβλητές παρουσίας και οι μεταβλητές κλάσης θα είναι ασφαλείς για νήματα μόνο εάν σχεδιάσετε την τάξη σας κατάλληλα.

RGBColor # 1: Έτοιμο για ένα νήμα

Ως παράδειγμα μιας τάξης που είναι δεν ασφαλές νήμα, σκεφτείτε το RGBColor τάξη, φαίνεται παρακάτω. Οι εμφανίσεις αυτής της κλάσης αντιπροσωπεύουν ένα χρώμα που είναι αποθηκευμένο σε τρεις ιδιωτικές μεταβλητές παρουσίας: ρ, σολ, και σι. Λαμβάνοντας υπόψη την τάξη που φαίνεται παρακάτω, ένα RGBColor το αντικείμενο θα ξεκινούσε τη ζωή του σε μια έγκυρη κατάσταση και θα αντιμετώπιζε μόνο μεταβάσεις έγκυρης κατάστασης, από την αρχή της ζωής του έως το τέλος - αλλά μόνο σε ένα μονό νήμα.

// Σε νήματα αρχείων / ex1 / RGBColor.java // Οι παρουσίες αυτής της κλάσης ΔΕΝ είναι ασφαλείς για θέματα. δημόσια τάξη RGBColor {private int r; ιδιωτικό int g; ιδιωτικό int b; δημόσιο RGBColor (int r, int g, int b) {checkRGBVals (r, g, b); αυτό.r = r; αυτό.g = g; αυτό.β = β; } public void setColor (int r, int g, int b) {checkRGBVals (r, g, b); αυτό.r = r; αυτό.g = g; αυτό.β = β; } / ** * επιστρέφει χρώμα σε μια σειρά τριών ints: R, G και B * / public int [] getColor () {int [] retVal = new int [3]; retVal [0] = r; retVal [1] = g; retVal [2] = b; επιστροφή retVal; } αναστροφή δημόσιου κενού () {r = 255 - r; g = 255 - g; b = 255 - β; } ιδιωτικό στατικό άκυρο έλεγχοRGBVals (int r, int g, int b) {if (r 255 || g 255 || b <0 || b> 255) {ρίξτε νέο IllegalArgumentException (); }}} 

Επειδή οι τρεις μεταβλητές παρουσίας, intμικρό ρ, σολ, και σι, είναι ιδιωτικοί, ο μόνος τρόπος με τον οποίο μπορούν να έχουν πρόσβαση ή να επηρεάσουν άλλες τιμές τάξεων και αντικειμένων τις τιμές αυτών των μεταβλητών RGBColorκατασκευαστής και μέθοδοι. Ο σχεδιασμός του κατασκευαστή και οι μέθοδοι εγγυώνται ότι:

  1. RGBColorΟ κατασκευαστής θα δίνει πάντα στις μεταβλητές τις κατάλληλες αρχικές τιμές

  2. Μέθοδοι setColor () και αντιστρέφω() θα εκτελεί πάντα έγκυρους μετασχηματισμούς κατάστασης σε αυτές τις μεταβλητές

  3. Μέθοδος getColor () θα επιστρέφει πάντα μια έγκυρη προβολή αυτών των μεταβλητών

Λάβετε υπόψη ότι εάν διαβιβαστούν κακά δεδομένα στον κατασκευαστή ή στο setColor () μέθοδος, θα ολοκληρωθούν απότομα με ένα Μη έγκυρη εξαίρεση. ο checkRGBVals () μέθοδος, που ρίχνει αυτήν την εξαίρεση, στην ουσία ορίζει τι σημαίνει για ένα RGBColor αντικείμενο να είναι έγκυρο: οι τιμές και των τριών μεταβλητών, ρ, σολ, και σι, πρέπει να είναι μεταξύ 0 και 255, συμπεριλαμβανομένων. Επιπλέον, για να είναι έγκυρο, το χρώμα που αντιπροσωπεύεται από αυτές τις μεταβλητές πρέπει να είναι το πιο πρόσφατο χρώμα είτε να περάσει στον κατασκευαστή είτε setColor () μέθοδο, ή παράγεται από το αντιστρέφω() μέθοδος.

Εάν, σε ένα μονό νήμα περιβάλλον, επικαλούμενος setColor () και περάστε με μπλε χρώμα, το RGBColor το αντικείμενο θα είναι μπλε όταν setColor () επιστρέφει. Εάν επικαλέστε τότε getColor () στο ίδιο αντικείμενο, θα έχετε μπλε. Σε μια μονομερή κοινωνία, περιπτώσεις αυτού RGBColor τάξη συμπεριφέρονται καλά.

Ρίχνει ταυτόχρονο κλειδί στα έργα

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

Συγκρούσεις εγγραφής / εγγραφής

Φανταστείτε ότι έχετε δύο νήματα, ένα νήμα που ονομάζεται "κόκκινο" και ένα άλλο που ονομάζεται "μπλε". Και τα δύο νήματα προσπαθούν να ορίσουν το ίδιο χρώμα RGBColor αντικείμενο: Το κόκκινο νήμα προσπαθεί να ρυθμίσει το χρώμα σε κόκκινο. το μπλε νήμα προσπαθεί να ρυθμίσει το χρώμα σε μπλε.

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

ο Μη συγχρονισμένη RGBColor μικροεφαρμογή

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

Για κάποιο λόγο, το πρόγραμμα περιήγησής σας δεν θα σας αφήσει να δείτε αυτό το δροσερό applet Java.

Για να ακολουθήσετε την ακολουθία συμβάντων που οδηγούν σε κατεστραμμένο RGBColor αντικείμενο, πατήστε το κουμπί Βήμα της μικροεφαρμογής. Πατήστε Πίσω για να δημιουργήσετε αντίγραφα ασφαλείας ενός βήματος και Επαναφορά για να δημιουργήσετε αντίγραφα ασφαλείας στην αρχή. Καθώς πηγαίνετε, μια γραμμή κειμένου στο κάτω μέρος της μικροεφαρμογής θα εξηγήσει τι συμβαίνει σε κάθε βήμα.

Για όσους από εσάς δεν μπορείτε να εκτελέσετε το applet, ακολουθεί ένας πίνακας που δείχνει την ακολουθία των συμβάντων που δείχνει η applet:

ΝήμαΔήλωσηρσολσιΧρώμα
κανέναςτο αντικείμενο αντιπροσωπεύει πράσινο02550 
μπλετο μπλε νήμα επικαλείται το setColor (0, 0, 255)02550 
μπλεcheckRGBVals (0, 0, 255);02550 
μπλεαυτό.r = 0;02550 
μπλεαυτό.g = 0;02550 
μπλετο μπλε γίνεται προληπτικό000 
το κόκκινοτο κόκκινο νήμα επικαλείται το setColor (255, 0, 0)000 
το κόκκινοcheckRGBVals (255, 0, 0);000 
το κόκκινοαυτό.r = 255;000 
το κόκκινοαυτό.g = 0;25500 
το κόκκινοαυτό.b = 0;25500 
το κόκκινοεπιστρέφει κόκκινο νήμα25500 
μπλεαργότερα, το μπλε νήμα συνεχίζεται25500 
μπλεαυτό.β = 25525500 
μπλεεπιστρέφει το μπλε νήμα2550255 
κανέναςτο αντικείμενο αντιπροσωπεύει ματζέντα2550255 

Όπως μπορείτε να δείτε από αυτό το applet και τον πίνακα, το RGBColor είναι κατεστραμμένο επειδή ο προγραμματιστής νημάτων διακόπτει το μπλε νήμα ενώ το αντικείμενο βρίσκεται σε προσωρινή μη έγκυρη κατάσταση. Όταν το κόκκινο νήμα μπαίνει και χρωματίζει το αντικείμενο με κόκκινο χρώμα, το μπλε νήμα ολοκληρώνεται μόνο εν μέρει στη βαφή του αντικειμένου μπλε. Όταν το μπλε νήμα επιστρέψει για να ολοκληρώσει την εργασία, καταστρέφει κατά λάθος το αντικείμενο.

Διαφωνίες ανάγνωσης / εγγραφής

Ένα άλλο είδος κακής συμπεριφοράς που μπορεί να εκδηλωθεί σε ένα περιβάλλον πολλαπλών νημάτων από περιπτώσεις αυτού RGBColor η τάξη είναι συγκρούσεις ανάγνωσης / εγγραφής. Αυτό το είδος διένεξης προκύπτει όταν η κατάσταση ενός αντικειμένου διαβάζεται και χρησιμοποιείται ενώ βρίσκεται σε προσωρινά άκυρη κατάσταση λόγω της ημιτελούς εργασίας ενός άλλου νήματος.

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

  1. Είναι προσωρινό: Τελικά, το μπλε νήμα σκοπεύει να ρυθμίσει το χρώμα σε μπλε.

  2. Δεν είναι έγκυρο: Κανείς δεν ζήτησε μαύρο RGBColor αντικείμενο. Το μπλε νήμα υποτίθεται ότι μετατρέπει ένα πράσινο αντικείμενο σε μπλε.

Εάν το μπλε νήμα είναι προεπιλεγμένο αυτή τη στιγμή, το αντικείμενο αντιπροσωπεύει μαύρο από ένα νήμα που επικαλείται getColor () στο ίδιο αντικείμενο, αυτό το δεύτερο νήμα θα παρατηρούσε το RGBColor η τιμή του αντικειμένου είναι μαύρο.

Ακολουθεί ένας πίνακας που δείχνει μια ακολουθία συμβάντων που θα μπορούσαν να οδηγήσουν σε μια τέτοια σύγκρουση ανάγνωσης / εγγραφής:

ΝήμαΔήλωσηρσολσιΧρώμα
κανέναςτο αντικείμενο αντιπροσωπεύει πράσινο02550 
μπλετο μπλε νήμα επικαλείται το setColor (0, 0, 255)02550 
μπλεcheckRGBVals (0, 0, 255);02550 
μπλεαυτό.r = 0;02550 
μπλεαυτό.g = 0;02550 
μπλετο μπλε γίνεται προληπτικό000 
το κόκκινοτο κόκκινο νήμα επικαλείται το getColor ()000 
το κόκκινοint [] retVal = νέο int [3];000 
το κόκκινοretVal [0] = 0;000 
το κόκκινοretVal [1] = 0;000 
το κόκκινοretVal [2] = 0;000 
το κόκκινοεπιστροφή retVal;000 
το κόκκινοτο κόκκινο νήμα επιστρέφει μαύρο000 
μπλεαργότερα, το μπλε νήμα συνεχίζεται000 
μπλεαυτό.β = 255000 
μπλεεπιστρέφει το μπλε νήμα00255 
κανέναςτο αντικείμενο αντιπροσωπεύει μπλε00255 

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

Τρεις τρόποι για να κάνετε ένα αντικείμενο ασφαλές στο νήμα

Υπάρχουν βασικά τρεις προσεγγίσεις που μπορείτε να ακολουθήσετε για να φτιάξετε ένα αντικείμενο όπως RGBΣήμα ασφαλές νήμα:

  1. Συγχρονίστε κρίσιμες ενότητες
  2. Κάντε το αμετάβλητο
  3. Χρησιμοποιήστε ένα περιτύλιγμα ασφαλείας για νήματα

Προσέγγιση 1: Συγχρονισμός των κρίσιμων ενοτήτων

Ο πιο απλός τρόπος για να διορθώσετε την απείθαρχη συμπεριφορά που επιδεικνύουν αντικείμενα όπως RGBColor όταν τοποθετείται σε ένα πολυ-νήμα περιβάλλον είναι να συγχρονίσετε τις κρίσιμες ενότητες του αντικειμένου. Ένα αντικείμενο κρίσιμα τμήματα είναι αυτές οι μέθοδοι ή μπλοκ κώδικα μέσα σε μεθόδους που πρέπει να εκτελούνται μόνο από ένα νήμα κάθε φορά. Με άλλα λόγια, μια κρίσιμη ενότητα είναι μια μέθοδος ή ένα μπλοκ κώδικα που πρέπει να εκτελεστεί ατομικά, ως μία, αδιαίρετη λειτουργία. Χρησιμοποιώντας Java's συγχρονισμένος λέξη-κλειδί, μπορείτε να εγγυηθείτε ότι μόνο ένα νήμα τη φορά θα εκτελεί ποτέ τις κρίσιμες ενότητες του αντικειμένου.

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

Βήμα 1: Κάντε ιδιωτικά τα πεδία

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

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