Programiranje

Java - zaznavanje in obdelava visečih niti

Avtor Alex. C. Punnen

Arhitekt - Nokia Siemens Networks

Bangalore

Viseče niti so pogost izziv pri razvoju programske opreme, ki se mora povezati z lastniškimi napravami z uporabo lastniških ali standardiziranih vmesnikov, kot so SNMP, Q3 ali Telnet. Ta težava ni omejena na upravljanje omrežja, ampak se pojavlja na številnih področjih, kot so spletni strežniki, procesi, ki kličejo oddaljene klice postopkov itd.

Nit, ki sproži zahtevo do naprave, potrebuje mehanizem za zaznavanje, če se naprava ne odzove ali se odzove le delno. V nekaterih primerih, ko je tak obesitev zaznan, je treba sprejeti posebne ukrepe. Konkretno dejanje je lahko ponovni preizkus ali obveščanje končnega uporabnika o neuspehu naloge ali kakšna druga možnost obnovitve. V nekaterih primerih, ko mora komponenta sprožiti veliko število nalog velikemu številu omrežnih elementov, je zaznavanje viseče niti pomembno, da ne postane ozko grlo za obdelavo drugih nalog. Torej obstajata dva vidika upravljanja visečih niti: izvedba in obvestilo.

Za vidik obveščanja vzorec Java Observer lahko prilagodimo tako, da se prilega večnitnemu svetu.

Prilagajanje vzorca Java Observer za večnitne sisteme

Zaradi visečih opravil z uporabo Jave ThreadPool razred s primerno strategijo je prva rešitev, ki mi pride na misel. Vendar z uporabo Jave ThreadPool v okviru nekaterih niti, ki naključno visijo v določenem časovnem obdobju, daje neželeno vedenje na podlagi določene uporabljene strategije, na primer stradanje niti v primeru strategije fiksnega nabora niti. To je predvsem posledica dejstva, da Java ThreadPool nima mehanizma za zaznavanje viseče niti.

Lahko bi preizkusili področje niti Cached, vendar ima tudi težave. Če obstaja velika hitrost sprožitve opravil in nekatere niti visijo, se lahko število niti poveča, kar sčasoma povzroči stradanje virov in izjeme iz pomnilnika. Lahko pa uporabimo Custom ThreadPool strategija, ki prikliče a CallerRunsPolicy. Tudi v tem primeru lahko zaradi prekinitve niti sčasoma visijo vse niti. (Glavna nit nikoli ne sme biti klicatelj, ker obstaja možnost, da lahko katera koli naloga, ki jo posreduje glavni nit, visi, zaradi česar se vse ustavi.)

Torej, kakšna je rešitev? Predstavil bom ne tako preprost vzorec ThreadPool, ki prilagodi velikost bazena glede na stopnjo opravila in glede na število visečih niti. Pojdimo najprej na problem odkrivanja visečih niti.

Odkrivanje visečih niti

Slika 1 prikazuje abstrakcijo vzorca:

Tu sta dva pomembna razreda: ThreadManager in ManagedThread. Oba segata od Jave Navoj razred. The ThreadManager vsebuje posodo, v kateri je ManagedThreads. Ko novo ManagedThread se ustvari, se doda v ta vsebnik.

 ThreadHangTester testthread = nov ThreadHangTester ("threadhangertest", 2000, napačno); testthread.start (); thrdManger.manage (testthread, ThreadManager.RESTART_THREAD, 10); thrdManger.start (); 

The ThreadManager pregleduje ta seznam in pokliče ManagedThreadje isHung () metoda. To je v bistvu logika preverjanja časovnega žiga.

 if (System.currentTimeMillis () - lastprocessingtime.get ()> maxprocessingtime) {logger.debug ("Nit je obešena"); vrni res; } 

Če ugotovi, da je nit šla v zanko opravil in ni nikoli posodobila svojih rezultatov, je potreben mehanizem za obnovitev, kot določa ManageThread.

 while (isRunning) {for (Iterator iterator = managedThreads.iterator (); iterator.hasNext ();) {ManagedThreadData thrddata = (ManagedThreadData) iterator.next (); if (thrddata.getManagedThread (). isHung ()) {logger.warn ("Zaznavanje niti je zaznano za ThreadName =" + thrddata.getManagedThread (). getName ()); switch (thrddata.getManagedAction ()) {case RESTART_THREAD: // Tukaj gre za ponovni zagon niti // odstranitev iz upravitelja iterator.remove (); // po možnosti ustavimo obdelavo te niti thrddata.getManagedThread (). stopProcessing (); if (thrddata.getManagedThread (). getClass () == ThreadHangTester.class) // Če želite vedeti, katero vrsto niti želite ustvariti {ThreadHangTester newThread = new ThreadHangTester ("restarted_ThrdHangTest", 5000, true); // Ustvari novo nit newThread.start (); // dodajte ga nazaj za upravljanje upravljanje (newThread, thrddata.getManagedAction (), thrddata.getThreadChecktime ()); } odmor; ......... 

Za novo ManagedThread ki naj se ustvari in uporablja namesto obešenega, ne sme vsebovati nobenega stanja ali nobenega vsebnika. Za to posoda, na kateri je ManagedThread akte je treba ločiti. Tu uporabljamo vzorec Singleton, ki temelji na ENUM, za zadrževanje seznama opravil. Torej je vsebnik, v katerem so naloge, neodvisen od niti, ki opravi naloge. Kliknite naslednjo povezavo za prenos vira za opisani vzorec: Java Thread Manager Source.

Viseče niti in strategije Java ThreadPool

Java ThreadPool nima mehanizma za zaznavanje visečih niti. Uporaba strategije, kot je fiksni navoj (Executors.newFixedThreadPool ()) ne bo delovala, ker če bodo nekatere naloge sčasoma visele, bodo vse niti sčasoma v visečem stanju. Druga možnost je uporaba predpomnjenega pravilnika ThreadPool (Executors.newCachedThreadPool ()). To bi lahko zagotovilo, da bodo za obdelavo naloge vedno na voljo niti, omejene le z omejitvami pomnilnika VM, CPE in niti. Vendar s tem pravilnikom ni nadzora nad številom niti, ki se ustvarijo. Ne glede na to, ali obdelovalna nit visi ali ne, uporaba tega pravilnika, medtem ko je stopnja opravil visoka, vodi do velikega števila niti. Če nimate dovolj sredstev za JVM, boste kmalu dosegli največji prag pomnilnika ali visok CPU. Precej pogosto je videti, da število niti doseže stotine ali tisoče. Čeprav se sprostijo, ko je naloga obdelana, včasih med hitrim ravnanjem veliko število niti preplavi sistemske vire.

Tretja možnost je uporaba strategij ali pravilnikov po meri. Ena takih možnosti je, da imamo področje niti, ki meri od 0 do nekega največjega števila. Torej, tudi če bi bila ena nit obešena, bi bila ustvarjena nova nit, dokler bi bilo doseženo največje število niti:

 execexec = new ThreadPoolExecutor (0, 3, 60, TimeUnit.SECONDS, nova SynchronousQueue ()); 

Tukaj 3 je največje število niti in čas vzdrževanja je nastavljen na 60 sekund, saj je to zahteven postopek. Če podajamo dovolj visoko največje število niti, je to bolj ali manj smiselno, da ga uporabimo v okviru obešanja nalog. Edina težava je v tem, da če viseče niti sčasoma ne bodo sproščene, obstaja majhna verjetnost, da bi vse niti v določenem trenutku lahko visele. Če je največ niti dovolj visoko in ob predpostavki, da je prekinitev opravil redek pojav, potem bi ta politika ustrezala računu.

Bilo bi lepo, če bi ThreadPool imel tudi vtični mehanizem za zaznavanje visečih niti. O enem takem dizajnu bom razpravljal kasneje. Če so vse niti zamrznjene, lahko nastavite in uporabite pravilnik o zavrnjenih opravilih področja niti. Če ne želite zavreči nalog, bi morali uporabiti CallerRunsPolicy:

 execexec = new ThreadPoolExecutor (0, 20, 20, TimeUnit.MILLISECONDS, new SynchronousQueue () new ThreadPoolExecutor.CallerRunsPolicy ()); 

V tem primeru, če je zaradi prekinitve niti naloga zavrnjena, bo ta naloga dana klicni niti, ki jo je treba obdelati. Vedno obstaja možnost, da ta naloga preveč visi. V tem primeru bi celoten postopek zamrznil. Zato je bolje, da takšne politike ne dodajamo v tem okviru.

 javni razred NotificationProcessor izvaja Runnable {zasebni končni NotificationOriginator notificationOrginator; logična isRunning = true; zasebni končni ExecutorService execexec; AlarmNotificationProcessor (NotificationOriginator norginator) {// ctor // execexec = Executors.newCachedThreadPool (); // Preveč niti // execexec = Executors.newFixedThreadPool (2); //, brez nalog za odkrivanje nalog execexec 0, novi 0, execexec = 0 , 250, TimeUnit.MILLISECONDS, new SynchronousQueue (), new ThreadPoolExecutor.CallerRunsPolicy ()); } public void run () {while (isRunning) {try {final Task task = TaskQueue.INSTANCE.getTask (); Runnable thisTrap = new Runnable () {public void run () {++ alarmid; notificaionOrginator.notify (new OctetString (), // Obdelava naloge nbialarmnew.getOID (), nbialarmnew.createVariableBindingPayload ()); É ........}}; execexec.execute (thisTrap); } 

ThreadPool po meri z zaznavanjem obeska

Bi bilo super imeti knjižnico niti z zmožnostjo zaznavanja in ravnanja s prekinitvami opravil. Razvil sem ga in ga bom prikazal spodaj. To so pravzaprav vrata iz nabora niti C ++, ki sem jih oblikoval in uporabil nekaj časa nazaj (glej reference). V osnovi ta rešitev uporablja vzorec Ukaz in Vzorec verige odgovornosti. Vendar je izvajanje ukaza Command v Javi brez pomoči funkcije Function support nekoliko težko. Za to sem moral nekoliko spremeniti izvedbo, da sem uporabil odsev Java. Upoštevajte, da je bil kontekst, v katerem je bil zasnovan ta vzorec, ta, da je bilo treba vstaviti / priključiti področje niti, ne da bi spremenili katerega koli obstoječega razreda. (Verjamem, da je ena velika prednost objektno usmerjenega programiranja ta, da nam daje možnost, da oblikujemo razrede tako, da učinkovito uporabimo odprto zaprto načelo. To še posebej velja za zapleteno staro zapuščinsko kodo in je morda manj pomembno za razvoj novega izdelka.) Zato sem namesto vmesnika za izvajanje vzorca Command uporabil refleksijo. Preostanek kode je mogoče prenesti brez večjih sprememb, saj so skoraj vsi primitivi za sinhronizacijo niti in signalizacijo na voljo v Javi 1.5 naprej.

 ukaz javnega razreda {private Object [] argParameter; ........ // Ctor za metodo z dvema ukazoma args (T pObj, String methodName, long timeout, String key, int arg1, int arg2) {m_objptr = pObj; m_methodName = mthodName; m_timeout = timeout; m_key = ključ; argParameter = nov objekt [2]; argParameter [0] = arg1; argParameter [1] = arg2; } // pokliče metodo predmeta void execute () {Class klass = m_objptr.getClass (); Class [] paramTypes = new Class [] {int.class, int.class}; poskusite {Method methodName = klass.getMethod (m_methodName, paramTypes); //System.out.println("Found the method -> "+ methodName); if (argParameter.length == 2) {methodName.invoke (m_objptr, (Object) argParameter [0], (Object) argParameter [1]); } 

Primer uporabe tega vzorca:

 javni razred CTask {.. javni int DoSomething (int a, int b) {...}} 

Ukaz cmd4 = nov ukaz (naloga4, "DoMultiplication", 1, "key2", 2,5);

Zdaj imamo tukaj še dva pomembna razreda. Eno je ThreadChain razred, ki izvaja vzorec verige odgovornosti:

 javni razred ThreadChain implementira Runnable {public ThreadChain (ThreadChain p, ThreadPool pool, ime niza) {AddRef (); deleteMe = false; zasedeno = napačno; // -> zelo pomembno next = p; // nastavimo verigo niti - upoštevajte, da je to kot povezan seznam impl threadpool = pool; // nastavimo področje niti - Koren bazena niti ........ threadId = ++ ThreadId; ...... // zaženi nit thisThread = new Thread (this, name + inttid.toString ()); thisThread.start (); } 

Ta razred ima dve glavni metodi. Ena je logična CanHandle () ki ga sproži ThreadPool razred in nato nadaljuje rekurzivno. Ta preveri, ali je trenutna nit (trenutna ThreadChain primer) lahko prosto opravi nalogo. Če že opravlja nalogo, pokliče naslednjo v verigi.

 public Boolean canHandle () {if (! busy) {// Če ni zaseden System.out.println ("Lahko obravnava ta dogodek v id =" + threadId); // opozorimo na dogodek, poskusite {condLock.lock (); condWait.signal (); // Signalizirajte zahtevo HandleRequest, ki čaka na to, v načinu izvajanja .................................... ..... vrni true; } ......................................... /// V nasprotnem primeru poglejte, če je naslednji predmet v verigi je brezplačen /// za obdelavo zahteve return next.canHandle (); 

Upoštevajte, da HandleRequest je metoda ThreadChain ki se prikliče iz Izvajanje niti () in čaka na signal iz canHandle metoda. Upoštevajte tudi, kako se opravilo obravnava prek vzorca Command.

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