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

Σύγχρονο νήμα: Ένα αστάρι ταυτόχρονης Java

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

Το Concurrency είναι από τις μεγαλύτερες ανησυχίες για τους νεοεισερχόμενους στον προγραμματισμό Java, αλλά δεν υπάρχει λόγος να το αφήσετε να σας τρομάξει. Όχι μόνο διατίθεται εξαιρετική τεκμηρίωση (θα εξερευνήσουμε πολλές πηγές σε αυτό το άρθρο), αλλά τα νήματα Java έχουν γίνει πιο εύκολο να δουλέψουν με την εξέλιξη της πλατφόρμας Java. Για να μάθετε πώς να κάνετε πολυνηματικό προγραμματισμό σε Java 6 και 7, χρειάζεστε πραγματικά κάποια δομικά στοιχεία. Θα ξεκινήσουμε με αυτά:

  • Ένα απλό πρόγραμμα με σπείρωμα
  • Το νήμα αφορά την ταχύτητα, σωστά;
  • Προκλήσεις της ταυτόχρονης Java
  • Πότε να χρησιμοποιήσετε το Runnable
  • Όταν τα καλά νήματα πάνε άσχημα
  • Τι νέο υπάρχει στο Java 6 και 7
  • Τι ακολουθεί για τα νήματα Java

Αυτό το άρθρο είναι μια έρευνα για αρχάριους σχετικά με τις τεχνικές νήματος Java, συμπεριλαμβανομένων συνδέσμων σε μερικά από τα πιο συχνά διαβασμένα εισαγωγικά άρθρα του JavaWorld σχετικά με τον προγραμματισμό πολλαπλών νημάτων. Ξεκινήστε τους κινητήρες σας και ακολουθήστε τους παραπάνω συνδέσμους εάν είστε έτοιμοι να αρχίσετε να μαθαίνετε για το Java threading σήμερα.

Ένα απλό πρόγραμμα με σπείρωμα

Εξετάστε την ακόλουθη πηγή Java.

Λίστα 1. FirstThreadingExample

class FirstThreadingExample {public static void main (String [] args) {// Το δεύτερο όρισμα είναι μια καθυστέρηση μεταξύ // διαδοχικών εξόδων. Η καθυστέρηση // μετράται σε χιλιοστά του δευτερολέπτου. Το "10", για παράδειγμα, σημαίνει, "εκτυπώστε μια γραμμή κάθε // εκατοστό του δευτερολέπτου". ExampleThread mt = νέο ExampleThread ("A", 31); ExampleThread mt2 = νέο ExampleThread ("B", 25); ExampleThread mt3 = νέο ExampleThread ("C", 10); mtstart (); mt2.start (); mt3.start (); }} Η κλάση ExampleThread επεκτείνει το νήμα {private int delay; δημόσιο ExampleThread (String label, int d) {// Δώστε σε αυτό το συγκεκριμένο νήμα ένα // όνομα: "νήμα 'LABEL'". σούπερ ("νήμα" "+ ετικέτα +" '"); καθυστέρηση = d; } δημόσια άκυρη εκτέλεση () {για (int count = 1, row = 1; row <20; row ++, count ++) {try {System.out.format ("Line #% d from% s \ n", count, getName ()) · Thread.currentThread (). Ύπνος (καθυστέρηση); } catch (InterruptException δηλαδή) {// Αυτό θα ήταν έκπληξη. }}}}

Τώρα μεταγλωττίστε και εκτελέστε αυτήν την πηγή όπως θα κάνατε με οποιαδήποτε άλλη εφαρμογή γραμμής εντολών Java. Θα δείτε έξοδο που μοιάζει με αυτό:

Λίστα 2. Παραγωγή προγράμματος με σπείρωμα

Γραμμή # 1 από το νήμα "A" Γραμμή # 1 από το νήμα "C" Γραμμή # 1 από το νήμα "B" Γραμμή # 2 από το νήμα "C" Γραμμή # 3 από το νήμα "C" Γραμμή # 2 από το νήμα "B" Γραμμή # 4 από το νήμα "C" ... Γραμμή # 17 από το νήμα "Β" Γραμμή # 14 από το νήμα "Α" Γραμμή # 18 από το νήμα "Β" Γραμμή # 15 από το νήμα "Α" Γραμμή # 19 από το νήμα "Β" Γραμμή # 16 από το νήμα "A" Γραμμή # 17 από το νήμα "A" Γραμμή # 18 από το νήμα "A" Γραμμή # 19 από το νήμα "A"

Αυτό είναι - είσαι Java Νήμα προγραμματιστής!

Λοιπόν, εντάξει, ίσως όχι τόσο γρήγορα. Όσο μικρότερο είναι το πρόγραμμα στη Λίστα 1, περιέχει μερικές λεπτές αποχρώσεις που αξίζουν την προσοχή μας.

Νήματα και αβεβαιότητα

Ένας τυπικός κύκλος μάθησης με προγραμματισμό αποτελείται από τέσσερα στάδια: (1) Μελέτη νέας ιδέας. (2) εκτέλεση προγράμματος δειγμάτων. (3) συγκρίνετε την παραγωγή με την προσδοκία. και (4) επαναλάβετε έως και τους δύο αγώνες. Σημειώστε, ωστόσο, ότι είπα προηγουμένως την έξοδο για FirstThreadingExample θα μοιάζει "κάτι σαν" Λίστα 2. Έτσι, αυτό σημαίνει ότι η παραγωγή σας θα μπορούσε να είναι διαφορετική από τη δική μου, γραμμή προς γραμμή. Τι είναι ότι σχετικά με?

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

Το Threading φέρνει νέα δύναμη στον προγραμματισμό Java. μπορείτε να επιτύχετε αποτελέσματα με νήματα που δεν θα μπορούσατε να κάνετε χωρίς αυτά. Αλλά αυτή η δύναμη έρχεται με κόστος αποφασιστικότητα. Στα απλούστερα προγράμματα Java, υπάρχει εγγύηση της σειράς εκτέλεσης: η πρώτη γραμμή στο κύριος() θα εκτελεστεί πρώτα, μετά το επόμενο και ούτω καθεξής, με την κατάλληλη ανίχνευση μέσα και έξω από άλλες μεθόδους. Νήμα αποδυναμώνει αυτήν την εγγύηση. Σε ένα πρόγραμμα πολλαπλών νημάτων, "Γραμμή # 17 από το νήμα Β"μπορεί να εμφανιστεί στην οθόνη σας πριν ή μετά"Γραμμή # 14 από το νήμα Α, "και η σειρά ενδέχεται να διαφέρει σε διαδοχικές εκτελέσεις του ίδιου προγράμματος, ακόμη και στον ίδιο υπολογιστή.

Η αβεβαιότητα μπορεί να είναι άγνωστη, αλλά δεν χρειάζεται να είναι ενοχλητική. Διάταξη εκτέλεσης στα πλαίσια ένα νήμα παραμένει προβλέψιμο, και υπάρχουν επίσης πλεονεκτήματα που σχετίζονται με την αβεβαιότητα. Ίσως να έχετε αντιμετωπίσει κάτι παρόμοιο όταν εργάζεστε με γραφικές διεπαφές χρήστη (GUI). Τα ακροατήρια συμβάντων στο Swing ή οι χειριστές συμβάντων σε HTML είναι παραδείγματα.

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

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

Καθυστέρηση εκτέλεσης και υποκατηγορία νήματος

Μπορείτε να μάθετε από FirstThreadingExample πειραματίζοντάς το μόνοι σας. Δοκιμάστε να προσθέσετε ή να καταργήσετε Παράδειγμα νήμαs - δηλαδή, επίκληση κατασκευαστή όπως ... νέο ExampleThread (ετικέτα, καθυστέρηση); - και παίζοντας με το καθυστέρησημικρό. Η βασική ιδέα είναι ότι το πρόγραμμα ξεκινά τρία ξεχωριστά Νήμαs, τα οποία στη συνέχεια λειτουργούν ανεξάρτητα μέχρι την ολοκλήρωση. Για να κάνουν την εκτέλεσή τους πιο διδακτική, κάθε μία καθυστερεί ελαφρώς μεταξύ των διαδοχικών γραμμών που γράφει στην έξοδο. Αυτό δίνει στα άλλα νήματα την ευκαιρία να γράψουν δικα τους παραγωγή.

Σημειώστε ότι ΝήμαΟ βασισμένος προγραμματισμός δεν απαιτεί, γενικά, χειρισμό ενός InterruptException. Αυτό που φαίνεται στο FirstThreadingExample έχει να κάνει με ύπνος(), αντί να σχετίζονται άμεσα με Νήμα. Πλέον Νήμα- η πηγή με βάση δεν περιλαμβάνει ύπνος(); ο σκοπός του ύπνος() εδώ είναι να μοντελοποιήσουμε, με έναν απλό τρόπο, τη συμπεριφορά των μακροχρόνιων μεθόδων που βρέθηκαν "στην άγρια ​​φύση".

Κάτι άλλο που πρέπει να παρατηρήσετε στη Λίστα 1 είναι αυτό Νήμα είναι ένα αφηρημένη τάξη, σχεδιασμένη για υποκατηγορία. Η προεπιλογή τρέξιμο() Η μέθοδος δεν κάνει τίποτα, επομένως πρέπει να παρακαμφθεί στον ορισμό της υποκατηγορίας για να επιτευχθεί οτιδήποτε χρήσιμο.

Αυτό αφορά την ταχύτητα, σωστά;

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

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

Σκεφτείτε μια εφαρμογή GUI: εάν εξακολουθεί να ανταποκρίνεται σε σημεία και κλικ τελικού χρήστη, ενώ αναζητάτε "στο παρασκήνιο" για ένα αντίστοιχο δακτυλικό αποτύπωμα ή υπολογίζετε ξανά το ημερολόγιο για το τουρνουά τένις του επόμενου έτους, τότε δημιουργήθηκε με γνώμονα την ταυτότητα. Μια τυπική ταυτόχρονη αρχιτεκτονική εφαρμογών δίνει αναγνώριση και απόκριση στις ενέργειες των χρηστών σε ένα νήμα ξεχωριστό από το υπολογιστικό νήμα που έχει ανατεθεί για να χειριστεί το μεγάλο φορτίο back-end (Ανατρέξτε στην ενότητα "Swing threading and the event-dispatch thread" για περαιτέρω απεικόνιση αυτών των αρχών.)

Στον δικό σας προγραμματισμό, λοιπόν, είναι πιθανότερο να σκεφτείτε να χρησιμοποιήσετε Νήμασε μία από αυτές τις περιπτώσεις:

  1. Μια υπάρχουσα εφαρμογή έχει σωστή λειτουργικότητα, αλλά κατά καιρούς δεν ανταποκρίνεται. Αυτά τα "μπλοκ" συχνά έχουν να κάνουν με εξωτερικούς πόρους εκτός ελέγχου σας: χρονοβόρα ερωτήματα βάσης δεδομένων, περίπλοκους υπολογισμούς, αναπαραγωγή πολυμέσων ή δικτυωμένες αποκρίσεις με ανεξέλεγκτη καθυστέρηση.
  2. Μια υπολογιστικά έντονη εφαρμογή θα μπορούσε να κάνει καλύτερη χρήση των πολλαπλών κεντρικών υπολογιστών. Αυτό μπορεί να ισχύει για κάποιον που δίνει πολύπλοκα γραφικά ή προσομοιώνει ένα εμπλεκόμενο επιστημονικό μοντέλο.
  3. Νήμα εκφράζει φυσικά το απαιτούμενο μοντέλο προγραμματισμού της εφαρμογής. Ας υποθέσουμε, για παράδειγμα, ότι μοντελοποιούσατε τη συμπεριφορά των οδηγών αυτοκινήτων ή των μελισσών σε μια κυψέλη. Για την εφαρμογή κάθε οδηγού ή μέλισσας ως ΝήμαΤο σχετικό αντικείμενο μπορεί να είναι βολικό από την άποψη του προγραμματισμού, εκτός από οποιεσδήποτε εκτιμήσεις της ταχύτητας ή της απόκρισης.

Προκλήσεις της ταυτόχρονης Java

Πρόσφατα, ο έμπειρος προγραμματιστής Ned Batchelder έχασε

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

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

Χειροτερεύει. Τα διαφορετικά νήματα μπορεί όχι μόνο να παράγουν αποτελέσματα σε διαφορετικές παραγγελίες, αλλά μπορούν υποστηρίζω σε πιο ουσιαστικά επίπεδα για αποτελέσματα. Είναι εύκολο για έναν νεοεισερχόμενο στο multithreading Κλείσε() μια λαβή αρχείου σε ένα Νήμα πριν από ένα διαφορετικό Νήμα έχει τελειώσει ό, τι χρειάζεται για να γράψει.

Δοκιμή ταυτόχρονων προγραμμάτων

Πριν από δέκα χρόνια στο JavaWorld, ο Dave Dyer σημείωσε ότι η γλώσσα της Java είχε ένα χαρακτηριστικό τόσο "διαδεδομένη χρήση λανθασμένα" που την χαρακτήρισε ως ένα σοβαρό ελάττωμα στο σχεδιασμό. Αυτό το χαρακτηριστικό ήταν πολλαπλών νημάτων.

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

Το σωστό σημείο εκκίνησης για την επίλυση των εγγενών δυσκολιών του ταυτόχρονου προγραμματισμού είχε δηλωθεί καλά από τον Heinz Kabutz στο ενημερωτικό δελτίο Java Specialist: αναγνωρίστε ότι το ταυτόχρονο είναι ένα θέμα που πρέπει να το κατανοήσετε και να το μελετήσετε συστηματικά. Υπάρχουν φυσικά εργαλεία όπως τεχνικές διαγράμματος και επίσημες γλώσσες που θα βοηθήσουν. Αλλά το πρώτο βήμα είναι να βελτιώσετε τη διαίσθησή σας με την εξάσκηση με απλά προγράμματα όπως FirstThreadingExample στη Λίστα 1. Στη συνέχεια, μάθετε όσο μπορείτε περισσότερα για βασικές πληροφορίες όπως αυτές:

  • Συγχρονισμός και αμετάβλητα αντικείμενα
  • Προγραμματισμός νημάτων και αναμονή / ειδοποίηση
  • Συνθήκες αγώνα και αδιέξοδο
  • Οθόνες νήματος για αποκλειστική πρόσβαση, προϋποθέσεις και ισχυρισμούς
  • Βέλτιστες πρακτικές JUnit - δοκιμή κώδικα πολλαπλών νημάτων

Πότε να χρησιμοποιήσετε το Runnable

Ο προσανατολισμός αντικειμένου στην Java ορίζει μοναδικά κληρονομικές τάξεις, οι οποίες έχουν συνέπειες για την κωδικοποίηση πολλαπλών νημάτων. Σε αυτό το σημείο, έχω περιγράψει μόνο μια χρήση για Νήμα που βασίστηκε σε υποκατηγορίες με μια παράκαμψη τρέξιμο(). Σε ένα σχέδιο αντικειμένων που περιλάμβανε ήδη κληρονομιά, αυτό απλά δεν θα λειτουργούσε. Δεν μπορείτε να κληρονομήσετε ταυτόχρονα RenderedObject ή Γραμμή παραγωγής ή ΜήνυμαQueue παραλληλα Νήμα!

Αυτός ο περιορισμός επηρεάζει πολλές περιοχές της Java, όχι μόνο το multithreading. Ευτυχώς, υπάρχει μια κλασική λύση για το πρόβλημα, με τη μορφή του Τρέξιμο διεπαφή. Όπως εξήγησε ο Jeff Friesen στην εισαγωγή του στο νήμα του 2002, το Τρέξιμο η διεπαφή δημιουργείται για καταστάσεις όπου η υποκατηγορία Νήμα δεν είναι δυνατό:

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

Έτσι για εκείνες τις τάξεις που δεν μπορούν να επεκταθούν Νήμα, πρέπει να δημιουργήσετε ένα runnable για να επωφεληθείτε από το multithreading. Σημασιολογικά, εάν κάνετε προγραμματισμό σε επίπεδο συστήματος και η τάξη σας είναι σε σχέση Νήμα, τότε θα πρέπει να υποκατηγορίες απευθείας από Νήμα. Όμως, η περισσότερη χρήση πολλαπλών νημάτων σε επίπεδο εφαρμογής εξαρτάται από τη σύνθεση, και έτσι ορίζει ένα Τρέξιμο συμβατό με το διάγραμμα κλάσης της εφαρμογής. Ευτυχώς, χρειάζεται μόνο μια επιπλέον γραμμή ή δύο για τον κωδικό χρησιμοποιώντας το Τρέξιμο διεπαφή, όπως φαίνεται στην καταχώριση 3 παρακάτω.