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

Java 101: Κατανόηση νημάτων Java, Μέρος 1: Εισαγωγή νημάτων και runnables

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

Λάβετε υπόψη ότι αυτό το άρθρο (μέρος των αρχείων JavaWorld) ενημερώθηκε με νέες καταχωρίσεις κώδικα και πηγαίο κώδικα με δυνατότητα λήψης τον Μάιο του 2013.

Κατανόηση των νημάτων Java - διαβάστε ολόκληρη τη σειρά

  • Μέρος 1: Παρουσίαση νημάτων και runnables
  • Μέρος 2: Συγχρονισμός
  • Μέρος 3: Προγραμματισμός νημάτων και αναμονή / ειδοποίηση
  • Μέρος 4: Ομάδες νημάτων και μεταβλητότητα

Τι είναι το νήμα;

Εννοιολογικά, η έννοια του α Νήμα δεν είναι δύσκολο να κατανοηθεί: είναι μια ανεξάρτητη διαδρομή εκτέλεσης μέσω του κώδικα προγράμματος. Όταν εκτελούνται πολλά νήματα, η διαδρομή ενός νήματος μέσω του ίδιου κώδικα διαφέρει συνήθως από τα άλλα. Για παράδειγμα, ας υποθέσουμε ότι ένα νήμα εκτελεί το ισοδύναμο κώδικα byte των δηλώσεων if-else αν μέρος, ενώ ένα άλλο νήμα εκτελεί το ισοδύναμο κώδικα byte του αλλού μέρος. Πώς παρακολουθεί η JVM την εκτέλεση κάθε νήματος; Το JVM δίνει σε κάθε νήμα τη δική του στοίβα μεθόδων-κλήσεων. Εκτός από την παρακολούθηση της τρέχουσας εντολής κώδικα byte, η στοίβα κλήσης μεθόδου παρακολουθεί τοπικές μεταβλητές, παραμέτρους που μεταβιβάζει το JVM σε μια μέθοδο και την τιμή επιστροφής της μεθόδου.

Όταν πολλά νήματα εκτελούν ακολουθίες εντολών byte-code στο ίδιο πρόγραμμα, αυτή η ενέργεια είναι γνωστή ως πολλαπλών νημάτων. Το Multithreading ωφελεί ένα πρόγραμμα με διάφορους τρόπους:

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

Η Java επιτυγχάνει multithreading μέσω της java.lang.Tread τάξη. Καθε Νήμα αντικείμενο περιγράφει ένα μόνο νήμα εκτέλεσης. Αυτή η εκτέλεση πραγματοποιείται στο Νήμα'μικρό τρέξιμο() μέθοδος. Επειδή η προεπιλογή τρέξιμο() μέθοδος δεν κάνει τίποτα, πρέπει να υποκατηγορίες Νήμα και παράκαμψη τρέξιμο() για να πετύχετε χρήσιμη δουλειά. Για μια γεύση νημάτων και πολλαπλών νημάτων στο πλαίσιο του Νήμα, εξετάστε την καταχώριση 1:

Λίστα 1. ThreadDemo.java

// ThreadDemo.java class ThreadDemo {public static void main (String [] args) {MyThread mt = νέο MyThread (); mtstart (); για (int i = 0; i <50; i ++) System.out.println ("i =" + i + ", i * i =" + i * i); }} Η κλάση MyThread επεκτείνει το νήμα {public void run () {for (int count = 1, row = 1; row <20; row ++, count ++) {for (int i = 0; i <count; i ++) System.out. Τυπώνω ('*'); System.out.print ('\ n'); }}}

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

Προγραμματισμός νημάτων και το JVM

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

Όταν πληκτρολογείτε java ThreadDemo για να εκτελέσετε την εφαρμογή, το JVM δημιουργεί ένα αρχικό νήμα εκτέλεσης, το οποίο εκτελεί το κύριος() μέθοδος. Εκτελώντας mtstart ();, το νήμα εκκίνησης λέει στο JVM να δημιουργήσει ένα δεύτερο νήμα εκτέλεσης που εκτελεί τις οδηγίες κώδικα byte που περιλαμβάνουν το Το MyThread αντικείμενο τρέξιμο() μέθοδος. Οταν ο αρχή() η μέθοδος επιστρέφει, το νήμα εκκίνησης εκτελεί το Για βρόχο για να εκτυπώσετε έναν πίνακα τετραγώνων, ενώ το νέο νήμα εκτελεί το τρέξιμο() μέθοδος για την εκτύπωση του ορθογώνιου τριγώνου.

Πώς φαίνεται η έξοδος; Τρέξιμο Νήμα Ντέμο για να μάθετε. Θα παρατηρήσετε ότι η έξοδος κάθε νήματος τείνει να διασταυρώνεται με την έξοδο του άλλου. Αυτό προκύπτει επειδή και τα δύο νήματα στέλνουν την έξοδο τους στην ίδια τυπική ροή εξόδου.

Η τάξη των νημάτων

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

Θα παρουσιάσω το υπόλοιπο του ΝήμαΜέθοδοι σε επόμενα άρθρα, με εξαίρεση τις καταργημένες μεθόδους της Sun.

Καταργημένες μέθοδοι

Η Sun έχει καταργήσει μια ποικιλία Νήμα μεθόδους, όπως αναστέλλω() και ΒΙΟΓΡΑΦΙΚΟ(), επειδή μπορούν να κλειδώσουν τα προγράμματά σας ή να καταστρέψουν αντικείμενα. Ως αποτέλεσμα, δεν πρέπει να τα καλέσετε στον κωδικό σας. Συμβουλευτείτε την τεκμηρίωση SDK για λύσεις σε αυτές τις μεθόδους. Δεν καλύπτω μεθόδους που έχουν καταργηθεί σε αυτήν τη σειρά.

Κατασκευή νημάτων

Νήμα έχει οκτώ κατασκευαστές. Τα πιο απλά είναι:

  • Νήμα(), το οποίο δημιουργεί ένα Νήμα αντικείμενο με προεπιλεγμένο όνομα
  • Νήμα (Όνομα συμβολοσειράς), το οποίο δημιουργεί ένα Νήμα αντικείμενο με ένα όνομα που το όνομα το όρισμα καθορίζει

Οι επόμενοι απλούστεροι κατασκευαστές είναι Νήμα (στόχος με δυνατότητα εκτέλεσης) και Νήμα (στόχος με δυνατότητα εκτέλεσης, όνομα συμβολοσειράς). Εκτός από το Τρέξιμο παραμέτρους, αυτοί οι κατασκευαστές είναι πανομοιότυποι με τους προαναφερθέντες κατασκευαστές. Η διαφορά: Το Τρέξιμο οι παράμετροι αναγνωρίζουν αντικείμενα έξω Νήμα που παρέχουν το τρέξιμο() μεθόδους. (Μάθετε για Τρέξιμο αργότερα σε αυτό το άρθρο.) Οι τέσσερις τελευταίοι κατασκευαστές μοιάζουν Νήμα (Όνομα συμβολοσειράς), Νήμα (στόχος με δυνατότητα εκτέλεσης), και Νήμα (στόχος με δυνατότητα εκτέλεσης, όνομα συμβολοσειράς); Ωστόσο, οι τελικοί κατασκευαστές περιλαμβάνουν επίσης ένα ThreadGroup επιχείρημα για οργανωτικούς σκοπούς.

Ένας από τους τέσσερις τελευταίους κατασκευαστές, Νήμα (ThreadGroup group, Runnable target, String name, long stackSize), είναι ενδιαφέρον γιατί σας επιτρέπει να καθορίσετε το επιθυμητό μέγεθος της στοίβας μεθόδου-κλήσης του νήματος. Το να μπορείτε να προσδιορίσετε ότι το μέγεθος αποδεικνύεται χρήσιμο σε προγράμματα με μεθόδους που χρησιμοποιούν αναδρομή - μια τεχνική εκτέλεσης με την οποία μια μέθοδο αποκαλείται επανειλημμένα - για την κομψή επίλυση ορισμένων προβλημάτων. Ορίζοντας ρητά το μέγεθος της στοίβας, μερικές φορές μπορείτε να το αποτρέψετε Σφάλμα StackOverflowμικρό. Ωστόσο, μπορεί να οδηγήσει σε πολύ μεγάλο μέγεθος Σφάλμα OutOfMemoryμικρό. Επίσης, η Sun θεωρεί ότι το μέγεθος της στοίβας κλήσεων μεθόδου εξαρτάται από την πλατφόρμα. Ανάλογα με την πλατφόρμα, το μέγεθος της στοίβας κλήσης μεθόδου ενδέχεται να αλλάξει. Επομένως, σκεφτείτε προσεκτικά τις επιπτώσεις στο πρόγραμμά σας προτού γράψετε κώδικα που καλεί Νήμα (ThreadGroup group, Runnable target, String name, long stackSize).

Ξεκινήστε τα οχήματά σας

Τα νήματα μοιάζουν με οχήματα: μετακινούν προγράμματα από την αρχή έως το τέλος. Νήμα και Νήμα τα αντικείμενα της υποκατηγορίας δεν είναι νήματα. Αντ 'αυτού, περιγράφουν τα χαρακτηριστικά ενός νήματος, όπως το όνομά του, και περιέχουν κώδικα (μέσω a τρέξιμο() μέθοδος) που εκτελεί το νήμα. Όταν έρθει η ώρα να εκτελεστεί ένα νέο νήμα τρέξιμο(), ένα άλλο νήμα καλεί το Νήμαή του αντικειμένου της υποκατηγορίας αρχή() μέθοδος. Για παράδειγμα, για να ξεκινήσετε ένα δεύτερο νήμα, το νήμα εκκίνησης της εφαρμογής - το οποίο εκτελείται κύριος()- κλήσεις αρχή(). Σε απάντηση, ο κώδικας χειρισμού νήματος της JVM λειτουργεί με την πλατφόρμα για να διασφαλίσει ότι το νήμα αρχικοποιείται σωστά και καλεί a Νήματου αντικειμένου ή της υποκατηγορίας του τρέξιμο() μέθοδος.

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

Το γράφημα δείχνει αρκετές σημαντικές χρονικές περιόδους:

  • Η αρχικοποίηση του νήματος εκκίνησης
  • Η στιγμή που το νήμα αρχίζει να εκτελείται κύριος()
  • Η στιγμή που το νήμα αρχίζει να εκτελείται αρχή()
  • Τη στιγμή αρχή() δημιουργεί ένα νέο νήμα και επιστρέφει στο κύριος()
  • Η προετοιμασία του νέου νήματος
  • Τη στιγμή που το νέο νήμα αρχίζει να εκτελείται τρέξιμο()
  • Οι διαφορετικές στιγμές κάθε νήμα τελειώνει

Σημειώστε ότι η προετοιμασία του νέου νήματος, η εκτέλεση του τρέξιμο(), και ο τερματισμός του συμβαίνει ταυτόχρονα με την εκτέλεση του αρχικού νήματος. Σημειώστε επίσης ότι μετά από ένα νήμα καλεί αρχή(), επακόλουθες κλήσεις σε αυτήν τη μέθοδο πριν από το τρέξιμο() μέθοδος εξέρχεται αιτία αρχή() να ρίξει ένα java.lang.IllegalThreadStateException αντικείμενο.

Τι υπάρχει στο όνομα;

Κατά τη διάρκεια μιας συνεδρίας εντοπισμού σφαλμάτων, η διάκριση ενός νήματος από το άλλο με φιλικό προς τον χρήστη τρόπο αποδεικνύεται χρήσιμη. Για να γίνει διάκριση μεταξύ νημάτων, η Java συσχετίζει ένα όνομα με ένα νήμα. Αυτό το όνομα είναι προεπιλεγμένο Νήμα, έναν ενωτικό χαρακτήρα και έναν ακέραιο αριθμό με βάση το μηδέν. Μπορείτε να αποδεχτείτε τα προεπιλεγμένα ονόματα νημάτων της Java ή μπορείτε να επιλέξετε τα δικά σας. Για να προσαρμόσετε προσαρμοσμένα ονόματα, Νήμα παρέχει κατασκευαστές που παίρνουν όνομα επιχειρήματα και α setName (Όνομα συμβολοσειράς) μέθοδος. Νήμα παρέχει επίσης ένα getName () μέθοδο που επιστρέφει το τρέχον όνομα. Η λίστα 2 δείχνει πώς να δημιουργήσετε ένα προσαρμοσμένο όνομα μέσω του Νήμα (Όνομα συμβολοσειράς) κατασκευαστή και ανακτήστε το τρέχον όνομα στο τρέξιμο() μέθοδο καλώντας getName ():

Λίστα 2. NameThatThread.java

// NameThatThread.java class NameThatThread {public static void main (String [] args) {MyThread mt; if (args.length == 0) mt = νέο MyThread (); αλλιώς mt = νέο MyThread (args [0]); mtstart (); }} Η κλάση MyThread επεκτείνει το νήμα {MyThread () {// Ο μεταγλωττιστής δημιουργεί το ισοδύναμο κώδικα byte του super (); } MyThread (Όνομα συμβολοσειράς) {super (name); // Μεταβείτε το όνομα στο Thread superclass} δημόσια ακύρωση () {System.out.println ("Το όνομά μου είναι:" + getName ()); }}

Μπορείτε να μεταβιβάσετε ένα προαιρετικό όρισμα ονόματος Το MyThread στη γραμμή εντολών. Για παράδειγμα, java NameThatThread X καθιερώνει Χ ως το όνομα του νήματος. Εάν δεν ορίσετε ένα όνομα, θα δείτε την ακόλουθη έξοδο:

Το όνομά μου είναι: Thread-1

Εάν προτιμάτε, μπορείτε να αλλάξετε το σούπερ (όνομα); καλέστε στο MyThread (Όνομα συμβολοσειράς) κατασκευαστής σε μια κλήση προς setName (Όνομα συμβολοσειράς)-όπως λέμε setName (όνομα);. Αυτή η τελευταία κλήση μεθόδου επιτυγχάνει τον ίδιο στόχο - τον καθορισμό του ονόματος του νήματος - όπως σούπερ (όνομα);. Το αφήνω ως άσκηση για σένα.

Ονομασία κύρια

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

Να κοιμάσαι ή να μην κοιμάσαι

Αργότερα σε αυτήν τη στήλη, θα σας παρουσιάσω κινουμένων σχεδίων- επανειλημμένα να σχεδιάζετε μια επιφάνεια επιφάνειας που διαφέρει ελαφρώς μεταξύ τους για να επιτύχετε μια ψευδαίσθηση κίνησης. Για να επιτευχθεί κινούμενη εικόνα, ένα νήμα πρέπει να σταματήσει κατά την εμφάνιση δύο διαδοχικών εικόνων. Κλήση Νήμαστατικό ύπνος (μακρά χιλιοστά) Η μέθοδος αναγκάζει ένα νήμα για παύση χιλιοστά χιλιοστά του δευτερολέπτου. Ένα άλλο νήμα θα μπορούσε ενδεχομένως να διακόψει το νήμα ύπνου. Εάν συμβεί αυτό, το νήμα ύπνου ξυπνά και ρίχνει ένα InterruptException αντικείμενο από το ύπνος (μακρά χιλιοστά) μέθοδος. Ως αποτέλεσμα, κωδικός που καλεί ύπνος (μακριά χιλιοστά) πρέπει να εμφανίζεται μέσα σε ένα δοκιμάστε μπλοκ - ή η μέθοδος του κώδικα πρέπει να περιλαμβάνει InterruptException μέσα στο ρίχνει ρήτρα.

Για να δείξει ύπνος (μακρά χιλιοστά), Έχω γράψει ένα Υπολογισμός 1 εφαρμογή. Αυτή η εφαρμογή ξεκινά ένα νέο νήμα που χρησιμοποιεί έναν μαθηματικό αλγόριθμο για τον υπολογισμό της τιμής της μαθηματικής σταθεράς pi. Ενώ υπολογίζεται το νέο νήμα, το νήμα έναρξης σταματά για 10 χιλιοστά του δευτερολέπτου κάνοντας κλήση ύπνος (μακρά χιλιοστά). Αφού ξυπνήσει το νήμα εκκίνησης, εκτυπώνει την τιμή pi, την οποία αποθηκεύει το νέο νήμα σε μεταβλητή πι. Η λίστα 3 παρουσιάζει Υπολογισμός 1πηγαίος κώδικας:

Λίστα 3. CalcPI1.java

// CalcPI1.java class CalcPI1 {public static void main (String [] args) {MyThread mt = νέο MyThread (); mtstart (); δοκιμάστε το {Thread.sleep (10); // Αναστολή για 10 χιλιοστά του δευτερολέπτου} catch (InterruptException e) {} System.out.println ("pi =" + mt.pi); }} Η κλάση MyThread επεκτείνει το νήμα {boolean negative = true; διπλό pi; // Αρχικοποιεί σε 0,0, από προεπιλογή δημόσια άκυρη εκτέλεση () {για (int i = 3; i <100000; i + = 2) {if (αρνητικό) pi - = (1.0 / i); αλλιώς pi + = (1.0 / i); αρνητικό =! αρνητικό; } pi + = 1.0; pi * = 4.0; System.out.println ("Ολοκληρώθηκε ο υπολογισμός PI"); }}

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

pi = -0.2146197014017295 Ολοκληρώθηκε ο υπολογισμός PI