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

Δημιουργήστε το δικό σας ObjectPool στην Java, Μέρος 1

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

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

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

Τώρα που τα βασικά είναι εκτός δρόμου, ας περάσουμε στον κώδικα. Αυτό είναι το σκελετικό αντικείμενο:

 δημόσια αφηρημένη τάξη ObjectPool {private long expiredTime; ιδιωτικό Hashtable κλειδωμένο, ξεκλείδωτο. abstract Object create (); abstract boolean validate (Object o); λήξη αφηρημένου κενού (Object o); συγχρονισμένο Object checkOut () {...} συγχρονισμένο void checkIn (Object o) {...}} 

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

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

 ObjectPool () {expiryTime = 30000; // 30 δευτερόλεπτα κλειδωμένο = νέο Hashtable (); ξεκλειδωμένο = νέο Hashtable (); } 

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

 συγχρονισμένο Object checkOut () {long now = System.currentTimeMillis (); Αντικείμενο o; if (unlocked.size ()> 0) {Enumeration e = unlocked.keys (); ενώ (e.hasMoreElements ()) {o = e.nextElement (); if ((now - ((Long) unlocked.get (o)) .longValue ())> expiredTime) {// το αντικείμενο έχει λήξει unlocked.remove (o); λήγει (o) · o = μηδέν; } αλλιώς {if (επικύρωση (o)) {unlocked.remove (o); lock.put (o, νέο Long (τώρα)); επιστροφή (o) · } αλλιώς {// αντικείμενο απέτυχε επικύρωση unlocked.remove (o); λήγει (o) · o = μηδέν; }}}} // δεν υπάρχουν διαθέσιμα αντικείμενα, δημιουργήστε ένα νέο o = create (); lock.put (o, νέο Long (τώρα)); επιστροφή (o) · } 

Αυτή είναι η πιο περίπλοκη μέθοδος στο ObjectPool τάξη, είναι όλα κατηφορικά από εδώ. ο check in () Η μέθοδος απλά μετακινεί το αντικείμενο που έχει περάσει από το κλειδωμένο hashtable στο ξεκλείδωτο hashtable.

συγχρονισμένο άκυρο checkIn (Object o) {lock.remove (o); unlocked.put (o, new Long (System.currentTimeMillis ())); } 

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

 δημόσια κλάση JDBCConnectionPool επεκτείνει το ObjectPool {private String dsn, usr, pwd; public JDBCConnectionPool () {...} create () {...} validate () {...} expire () {...} public Connection loanConnection () {...} public void returnConnection () {. ..}} 

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

 public JDBCConnectionPool (String driver, String dsn, String usr, String pwd) {δοκιμάστε {Class.forName (driver) .newInstance (); } catch (Εξαίρεση e) {e.printStackTrace (); } αυτό.dsn = dsn; this.usr = usr; αυτό.pwd = pwd; } 

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

 Δημιουργία αντικειμένου () {try {return (DriverManager.getConnection (dsn, usr, pwd)); } catch (SQLException e) {e.printStackTrace (); επιστροφή (μηδέν) }} 

Πριν το ObjectPool ελευθερώνει ένα ληγμένο (ή μη έγκυρο) αντικείμενο για συλλογή απορριμμάτων, το μεταδίδει στην υποκατηγορία του εκπνέω() μέθοδος για κάθε απαραίτητο καθαρισμό της τελευταίας στιγμής (πολύ παρόμοιο με το Οριστικοποιώ() μέθοδος που καλείται από τον συλλέκτη απορριμμάτων). Σε περίπτωση που JDBCConnectionPool, το μόνο που πρέπει να κάνουμε είναι να κλείσουμε τη σύνδεση.

void expire (Object o) {try {((Connection) o) .close (); } catch (SQLException e) {e.printStackTrace (); }} 

Και τέλος, πρέπει να εφαρμόσουμε τη μέθοδο validate () που ObjectPool κλήσεις για να βεβαιωθείτε ότι ένα αντικείμενο εξακολουθεί να ισχύει για χρήση. Αυτό είναι επίσης το μέρος που πρέπει να πραγματοποιηθεί οποιαδήποτε εκ νέου προετοιμασία. Για JDBCConnectionPool, απλά ελέγχουμε για να δούμε ότι η σύνδεση είναι ακόμα ανοιχτή.

 boolean validate (Object o) {try {return (! (Connection) o) .isClosed ()); } catch (SQLException e) {e.printStackTrace (); επιστροφή (false); }} 

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

 public Connection loanConnection () {return ((Connection) super.checkOut ()); } public void returnConnection (Σύνδεση c) {super.checkIn (c); } 

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

Ο Thomas E. Davis είναι πιστοποιημένος από την Sun προγραμματιστής Java. Σήμερα κατοικεί στην ηλιόλουστη Νότια Φλόριντα, αλλά υποφέρει ως εργασιομανής και περνά τον περισσότερο χρόνο του σε εσωτερικούς χώρους.

Αυτή η ιστορία, "Δημιουργήστε το δικό σας ObjectPool στην Java, Μέρος 1" δημοσιεύθηκε αρχικά από το JavaWorld.