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

Εισαγωγή στα νήματα Java

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

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

Μαθαίνοντας για νήματα Java

Αυτό το άρθρο είναι μέρος του αρχείου τεχνικού περιεχομένου JavaWorld. Δείτε τα παρακάτω για να μάθετε περισσότερα σχετικά με τα νήματα Java και το ταυτόχρονο:

Κατανόηση των νημάτων Java (Java 101 σειρά, 2002):

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

Σχετικά Άρθρα

  • Υπερ-νήμα Java: Χρήση του Java Concurrency API (2006)
  • Καλύτερες οθόνες για προγράμματα πολλαπλών νημάτων (2007)
  • Κατανόηση της ταυτότητας του ηθοποιού, Μέρος 1 (2009)
  • Ανίχνευση και χειρισμός κλωστής (2011)

Ελέγξτε επίσης τον JavaWorld χάρτης ιστότοπου και μηχανή αναζήτησης.

Οι εφαρμογές πολλαπλών νημάτων προσφέρουν την ισχυρή ισχύ τους εκτελώντας ταυτόχρονα πολλά νήματα σε ένα μόνο πρόγραμμα. Από λογική άποψη, το multithreading σημαίνει ότι πολλές γραμμές ενός μεμονωμένου προγράμματος μπορούν να εκτελεστούν ταυτόχρονα, ωστόσο, δεν είναι το ίδιο με την εκκίνηση ενός προγράμματος δύο φορές και λέγοντας ότι υπάρχουν πολλές γραμμές ενός προγράμματος που εκτελούνται ταυτόχρονα χρόνος. Σε αυτήν την περίπτωση, το λειτουργικό σύστημα αντιμετωπίζει τα προγράμματα ως δύο ξεχωριστές και ξεχωριστές διαδικασίες. Στην ενότητα Unix, η διαμόρφωση μιας διαδικασίας δημιουργεί μια θυγατρική διαδικασία με διαφορετικό χώρο διευθύνσεων τόσο για τον κώδικα όσο και για τα δεδομένα. Ωστόσο, πιρούνι() δημιουργεί πολλά γενικά έξοδα για το λειτουργικό σύστημα, καθιστώντας το μια πολύ εντατική λειτουργία CPU. Αν ξεκινήσετε ένα νήμα αντ 'αυτού, δημιουργείται μια αποτελεσματική διαδρομή εκτέλεσης ενώ εξακολουθείτε να μοιράζεστε την αρχική περιοχή δεδομένων από τον γονέα. Η ιδέα της κοινοποίησης της περιοχής δεδομένων είναι πολύ ωφέλιμη, αλλά φέρνει κάποιους τομείς ανησυχίας που θα συζητήσουμε αργότερα.

Δημιουργία νημάτων

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

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

Υπάρχουν μερικές διαφορές μεταξύ μιας κλάσης και μιας διεπαφής. Πρώτον, μια διεπαφή μπορεί να περιέχει μόνο αφηρημένες μεθόδους και / ή στατικές τελικές μεταβλητές (σταθερές). Τα μαθήματα, από την άλλη πλευρά, μπορούν να εφαρμόσουν μεθόδους και να περιέχουν μεταβλητές που δεν είναι σταθερές. Δεύτερον, μια διεπαφή δεν μπορεί να εφαρμόσει μεθόδους. Μια κλάση που εφαρμόζει μια διεπαφή πρέπει να εφαρμόζει όλες τις μεθόδους που ορίζονται σε αυτήν τη διεπαφή. Μια διεπαφή έχει τη δυνατότητα επέκτασης από άλλες διεπαφές και (σε ​​αντίθεση με τις κλάσεις) μπορεί να επεκταθεί από πολλές διεπαφές. Επιπλέον, δεν μπορεί να δημιουργηθεί μια διεπαφή με τον νέο χειριστή. για παράδειγμα, Runnable a = new Runnable (); δεν επιτρέπεται.

Η πρώτη μέθοδος δημιουργίας ενός νήματος είναι απλώς να επεκταθεί από το Νήμα τάξη. Κάντε αυτό μόνο εάν η κλάση που θέλετε να εκτελεστεί ως νήμα δεν χρειάζεται ποτέ να επεκταθεί από άλλη τάξη. ο Νήμα Η κλάση ορίζεται στο πακέτο java.lang, το οποίο πρέπει να εισαχθεί έτσι ώστε οι τάξεις μας να γνωρίζουν τον ορισμό της.

εισαγωγή java.lang. *; Μετρητής δημόσιας τάξης επεκτείνει το νήμα {public void run () {....}}

Το παραπάνω παράδειγμα δημιουργεί μια νέα τάξη Μετρητής που επεκτείνει το Νήμα τάξη και αντικαθιστά το Thread.run () μέθοδο για τη δική του εφαρμογή. ο τρέξιμο() μέθοδος είναι όπου όλη η εργασία του Μετρητής το νήμα της τάξης ολοκληρώθηκε. Η ίδια τάξη μπορεί να δημιουργηθεί εφαρμόζοντας το Runnable:

εισαγωγή java.lang. *; Μετρητής δημόσιας τάξης υλοποιεί Runnable {Thread T; δημόσια άκυρη εκτέλεση () {....}}

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

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

πακέτο java.lang; δημόσια διεπαφή Runnable {public abstract void run (); }

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

Δημόσια κλάση νήμα υλοποιεί Runnable {... public void run () {if (target! = null) {target.run (); }} ...}

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

Ξεκινώντας και σταματώντας

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

Παράδειγμα CounterThread και πηγαίος κώδικας

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

Σε αυτήν την περίπτωση, το CounterThread Η τάξη αναγκάστηκε να εφαρμόσει το Runnable αφού επέκτεινε την κλάση Applet. Όπως σε όλες τις μικροεφαρμογές, το μέσα σε αυτό() η μέθοδος εκτελείται πρώτα. Σε μέσα σε αυτό(), η μεταβλητή Count αρχικοποιείται στο μηδέν και μια νέα παρουσία του Νήμα δημιουργείται τάξη. Περνώντας Αυτό στο Νήμα κατασκευαστής, το νέο νήμα θα ξέρει ποιο αντικείμενο να τρέξει. Σε αυτήν την περίπτωση Αυτό είναι μια αναφορά στο CounterThread. Μετά τη δημιουργία του νήματος πρέπει να ξεκινήσει. Η κλήση προς αρχή() θα καλέσει το στόχο τρέξιμο() μέθοδος, η οποία είναι CounterThread.τρέξιμο(). Η κλήση προς αρχή() θα επιστρέψει αμέσως και το νήμα θα αρχίσει να εκτελείται ταυτόχρονα. Σημειώστε ότι το τρέξιμο() μέθοδος είναι ένας άπειρος βρόχος. Είναι άπειρο γιατί μόλις το τρέξιμο() η μέθοδος εξέρχεται, το νήμα σταματά να εκτελείται. ο τρέξιμο() Η μέθοδος θα αυξήσει τη μεταβλητή Count, sleep για 10 χιλιοστά του δευτερολέπτου και θα στείλει ένα αίτημα για ανανέωση της οθόνης της applet.

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

Αναστολή και συνέχιση

Μόλις σταματήσει ένα νήμα, δεν μπορεί να γίνει επανεκκίνηση με το αρχή() εντολή, από τότε να σταματήσει() θα τερματίσει την εκτέλεση ενός νήματος. Αντ 'αυτού μπορείτε να διακόψετε την εκτέλεση ενός νήματος με το ύπνος() μέθοδος. Το νήμα θα αδράξει για ένα ορισμένο χρονικό διάστημα και στη συνέχεια θα αρχίσει να εκτελείται όταν φτάσει το χρονικό όριο. Όμως, αυτό δεν είναι ιδανικό εάν το νήμα πρέπει να ξεκινήσει όταν συμβαίνει ένα συγκεκριμένο συμβάν. Σε αυτήν την περίπτωση, το αναστέλλω() μέθοδος επιτρέπει σε ένα νήμα να σταματήσει προσωρινά την εκτέλεση και το ΒΙΟΓΡΑΦΙΚΟ() Η μέθοδος επιτρέπει στο εκκρεμές νήμα να ξεκινήσει ξανά. Η ακόλουθη μικροεφαρμογή δείχνει το παραπάνω παράδειγμα τροποποιημένο για αναστολή και συνέχιση της μικροεφαρμογής.

δημόσια τάξη CounterThread2 επεκτείνει Applet υλοποιεί Runnable {Thread t; int Count; boolean σε αναστολή δημόσιο boolean mouseDown (Event e, int x, int y) {if (σε αναστολή) t.resume (); αλλιώς t.suspend (); ανασταλεί =! ανασταλεί; επιστροφή αληθινή? } ...}

CounterThread2 Παράδειγμα και πηγαίος κώδικας

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