Ohjelmointi

Java - riippuvan langan tunnistus ja käsittely

Tekijä Alex. C. Punnen

Arkkitehti - Nokia Siemens Networks

Bangalore

Riippuvat langat ovat yleinen haaste sellaisten ohjelmistojen kehittämisessä, joiden on liityttävä omien laitteidensa kanssa käyttämällä omia tai standardoituja rajapintoja, kuten SNMP, Q3 tai Telnet. Tämä ongelma ei rajoitu verkonhallintaan, mutta sitä esiintyy monilla aloilla, kuten verkkopalvelimet, prosessit, jotka kutsuvat etäkäyttökutsuja, ja niin edelleen.

Lanka, joka käynnistää pyynnön laitteelle, tarvitsee mekanismin havaitsemiseksi, jos laite ei vastaa tai vastaa vain osittain. Joissakin tapauksissa, kun tällainen jumittuminen havaitaan, on tehtävä erityinen toimenpide. Erityinen toimenpide voi olla joko uudelleenkäsittely tai kertoa loppukäyttäjälle tehtävän epäonnistumisesta tai jokin muu palautusvaihtoehto. Joissakin tapauksissa, joissa komponentin on käynnistettävä suuri määrä tehtäviä suurelle määrälle verkkoelementtejä, riippuvan langan tunnistus on tärkeää, jotta siitä ei tule pullonkaula muuhun tehtävän käsittelyyn. Joten ripustettujen säikeiden hallinnassa on kaksi näkökohtaa: esitys ja ilmoituksen.

Varten ilmoituksen näkökulma voimme räätälöidä Java Observer -kuvion sopivaksi monisäikeiseen maailmaan.

Java Observer -mallin räätälöinti monisäikeisiin järjestelmiin

Javan käyttäminen roikkuvien tehtävien takia ThreadPool luokka, jolla on sopiva strategia, on ensimmäinen mieleen tuleva ratkaisu. Kuitenkin Java ThreadPool Joidenkin ketjujen satunnainen ripustaminen tietyn ajanjakson aikana antaa ei-toivottua käyttäytymistä käytetyn strategian perusteella, kuten langan nälkää kiinteän langan poolistrategian tapauksessa. Tämä johtuu pääasiassa siitä, että Java ThreadPool ei ole mekanismia langan jumittumisen havaitsemiseksi.

Voisimme kokeilla välimuistilanka-allasta, mutta sillä on myös ongelmia. Jos tehtävän käynnistysaste on suuri ja jotkut säikeet roikkuvat, ketjujen määrä saattaa nousta ylös, mikä lopulta aiheuttaa resurssien nälkää ja muistin ulkopuolella olevia poikkeuksia. Tai voisimme käyttää mukautettua ThreadPool strategia vedoten a CallerRunsPolicy. Myös tässä tapauksessa langankiinnitys voi saada kaikki langat roikkumaan lopulta. (Pääketju ei saa koskaan olla soittaja, koska on mahdollista, että mikä tahansa päälangalle välitettävä tehtävä voi jumittua, mikä saa kaiken pysähtymään.)

Joten mikä on ratkaisu? Esittelen ei-niin yksinkertaisen ThreadPool-mallin, joka säätää altaan koon tehtävänopeuden mukaan ja riippuvien lankojen määrän mukaan. Siirrytään ensin riippuvien lankojen havaitsemiseen.

Riippuvien lankojen tunnistaminen

Kuva 1 esittää abstraktin kuviosta:

Täällä on kaksi tärkeää luokkaa: ThreadManager ja ManagedThread. Molemmat ulottuvat Java-alueelta Lanka luokassa. ThreadManager omistaa kontin, johon mahtuu ManagedThreads. Kun uusi ManagedThread on luotu, se lisää itsensä tähän säiliöön.

 ThreadHangTester testthread = uusi ThreadHangTester ("threadhangertest", 2000, väärä); testthread.start (); thrdManger.manage (testilanka, ThreadManager.RESTART_THREAD, 10); thrdManger.start (); 

ThreadManager iteroi tämän luettelon läpi ja kutsuu ManagedThreadon isHung () menetelmä. Tämä on periaatteessa aikaleiman tarkistuslogiikka.

 if (System.currentTimeMillis () - lastprocessingtime.get ()> maxprocessingtime) {logger.debug ("Kierre on ripustettu"); palaa tosi; } 

Jos se havaitsee, että ketju on mennyt tehtäväsilmukkaan eikä ole koskaan päivittänyt tuloksiaan, se vie palautusmekanismin ManageThread.

 while (isRunning) {for (Iterator iterator = managedThreads.iterator (); iterator.hasNext ();) {ManagedThreadData thrddata = (ManagedThreadData) iterator.next (); if (thrddata.getManagedThread (). isHung ()) {logger.warn ("Langankierto havaittu langalle Nimi =" + thrddata.getManagedThread (). getName ()); switch (thrddata.getManagedAction ()) {tapaus RESTART_THREAD: // Tässä toiminto on käynnistää ketju uudelleen // poistaa ohjaimesta iterator.remove (); // lopeta tämän säikeen käsittely, jos mahdollista thrddata.getManagedThread (). stopProcessing (); if (thrddata.getManagedThread (). getClass () == ThreadHangTester.class) // Tietää minkä tyyppinen ketju luodaan {ThreadHangTester newThread = new ThreadHangTester ("restarted_ThrdHangTest", 5000, true); // Luo uusi ketju newThread.start (); // lisää se takaisin hallittavaan hallintaan (newThread, thrddata.getManagedAction (), thrddata.getThreadChecktime ()); } tauko; ......... 

Uutta varten ManagedThread luodaan ja käytetään ripustetun sijasta, siinä ei saa olla tilaa tai konttia. Tätä varten astia, jolla ManagedThread teot olisi erotettava toisistaan. Tässä käytämme ENUM-pohjaista Singleton-mallia tehtäväluettelon pitämiseen. Joten kontti, jolla on tehtävät, on riippumaton tehtäviä käsittelevästä säikeestä. Napsauta seuraavaa linkkiä ladataksesi kuvatun mallin lähde: Java Thread Manager Source.

Riippuvat langat ja Java ThreadPool -strategiat

Java ThreadPool siinä ei ole mekanismia riippuvien lankojen havaitsemiseksi. Käyttämällä strategiaa, kuten kiinteä threadpool (Executors.newFixedThreadPool ()) ei toimi, koska jos jotkut tehtävät jumittuvat ajan myötä, kaikki ketjut ovat lopulta ripustetussa tilassa. Toinen vaihtoehto on käyttää välimuistissa olevaa ThreadPool-käytäntöä (Executors.newCachedThreadPool ()). Tämä voisi varmistaa, että tehtävän käsittelyyn on aina käytettävissä säikeitä, joita vain VM-muisti, suorittimen ja säikeiden rajoitukset rajoittavat. Tämän käytännön avulla ei kuitenkaan voida hallita luotavien ketjujen määrää. Riippumatta siitä, onko käsittelylanka jumissa vai ei, tämän käytännön käyttö, kun tehtävämäärä on korkea, johtaa valtavan määrän ketjujen luomiseen. Jos sinulla ei ole tarpeeksi resursseja JVM: lle hyvin pian, saavutat muistin enimmäiskynnyksen tai korkean suorittimen. On melko yleistä nähdä satojen tai tuhansien osumien lukumäärä. Vaikka ne vapautetaan tehtävän käsittelyn jälkeen, joskus purskeen käsittelyn aikana, ketjujen suuri määrä ylikuormittaa järjestelmän resurssit.

Kolmas vaihtoehto on käyttää mukautettuja strategioita tai käytäntöjä. Yksi tällainen vaihtoehto on, että ketjupooli skaalaa 0: sta johonkin enimmäismäärään. Joten vaikka yksi ketju ripustettaisiin, uusi ketju luodaan niin kauan kuin maksimilanka saavutetaan:

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

Tässä 3 on säikeiden enimmäismäärä ja eloonjäämisaika on asetettu 60 sekuntiin, koska tämä on tehtäväintensiivinen prosessi. Jos annamme riittävän korkean säikeiden enimmäismäärän, tämä on enemmän tai vähemmän järkevä käytäntö, jota käytetään riippuvien tehtävien yhteydessä. Ainoa ongelma on, että jos riippuvia lankoja ei lopulta vapauteta, on pieni mahdollisuus, että kaikki langat voivat jossakin vaiheessa roikkua. Jos enimmäislangat ovat riittävän korkeat ja olettaen, että tehtävän jumittuminen on harvinaista, niin tämä käytäntö sopisi laskuun.

Olisi ollut makeaa, jos ThreadPool sillä oli myös liitettävä mekanismi roikkuvien kierteiden havaitsemiseksi. Keskustelen yhdestä tällaisesta mallista myöhemmin. Tietysti, jos kaikki ketjut ovat jäädytetyt, voit määrittää ja käyttää ketjupoolin hylättyjen tehtävien käytäntöä. Jos et halua hylätä tehtäviä, sinun on käytettävä CallerRunsPolicy:

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

Tässä tapauksessa, jos ketjun jumitus johti tehtävän hylkäämiseen, kyseinen tehtävä annettaisiin käsiteltävälle kutsulangalle. Aina on mahdollisuus, että tehtävä riippuu liian. Tässä tapauksessa koko prosessi jäätyy. Joten on parempi olla lisäämättä tällaista politiikkaa tässä yhteydessä.

 public class NotificationProcessor toteuttaa Runnable {private final NotificationOriginator notificationOrginator; totuusarvo isRunning = true; yksityinen lopullinen ExecutorService execexec; AlarmNotificationProcessor (NotificationOriginator norginator) {// ctor // execexec = Executors.newCachedThreadPool (); // Liian monta säiettä // execexec = Executors.newFixedThreadPool (2); //, ei roikkuvien tehtävien havaitsemista execexec = uusi ThreadPoolExor , 250, TimeUnit.MILLISECONDS, uusi SynchronousQueue (), uusi ThreadPoolExecutor.CallerRunsPolicy ()); } public void run () {while (isRunning) {kokeile {final Task task = TaskQueue.INSTANCE.getTask (); Runnable thisTrap = new Runnable () {public void run () {++ alarmid; notificaionOrginator.notify (uusi OctetString (), // Tehtävän käsittely nbialarmnew.getOID (), nbialarmnew.createVariableBindingPayload ()); É ........}}; execexec.execute (thisTrap); } 

Mukautettu ThreadPool, jossa on Hang Detection

Langallinen allaskirjasto, jolla on tehtävän jumittumisen havaitsemis- ja käsittelyominaisuudet, olisi hieno. Olen kehittänyt sellaisen ja esitän sen alla. Tämä on oikeastaan ​​portti C ++ -säikejoukosta, jonka suunnittelin ja käytin jonkin aikaa sitten (katso viitteet). Pohjimmiltaan tämä ratkaisu käyttää komentokuviota ja vastuullisuuden ketjua. Komento-mallin toteuttaminen Java-ohjelmassa ilman toiminto-objektitukea on kuitenkin hieman vaikeaa. Tätä varten minun piti muuttaa toteutusta hieman käyttääksesi Java-heijastusta. Huomaa, että konteksti, jossa tämä kuvio suunniteltiin, oli, että kierreallas oli asennettava / kytketty muuttamatta mitään olemassa olevista luokista. (Uskon, että olio-ohjelmoinnin yksi suuri etu on, että se antaa meille mahdollisuuden suunnitella luokkia niin, että voimme käyttää tehokkaasti avointa suljettua periaatetta. Tämä pätee erityisesti monimutkaisiin vanhoihin vanhoihin koodeihin ja sillä voi olla vähemmän merkitystä uuden tuotekehityksen.) Siksi käytin pohdintaa sen sijaan, että käyttäisin komentorivin toteuttamiseen käyttöliittymää. Loput koodista voitaisiin siirtää ilman suuria muutoksia, koska melkein kaikki langan synkronointi- ja merkinantoprimitiivit ovat saatavana Java 1.5: sta eteenpäin.

 julkinen luokka Komento {yksityinen objekti [] argParameter; ........ // Ctor menetelmälle, jolla on kaksi argumenttia Komento (T pObj, String methodName, pitkä aikakatkaisu, String-avain, int arg1, int arg2) {m_objptr = pObj; m_metodinimi = mthodNAME; m_timeout = aikakatkaisu; m_avain = avain; argParameter = uusi objekti [2]; argParametri [0] = arg1; argParametri [1] = arg2; } // Kutsuu objektin void execute () menetelmää {Class klass = m_objptr.getClass (); Luokka [] paramTypes = uusi luokka [] {sis. Luokka, sis. Luokka}; kokeile {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]); } 

Esimerkki tämän mallin käytöstä:

 julkisen luokan CTask {.. public int DoSomething (int a, int b) {...}} 

Komento cmd4 = uusi komento (tehtävä 4, "DoMultiplication", 1, "avain2", 2,5);

Nyt meillä on täällä vielä kaksi tärkeää luokkaa. Yksi on LankaKetju luokka, joka toteuttaa vastuullisuusketjun mallin:

 public class ThreadChain toteuttaa Runnable {public ThreadChain (ThreadChain p, ThreadPool pool, String name) {AddRef (); deleteMe = väärä; varattu = väärä; // -> erittäin tärkeä seuraava = p; // aseta ketjuketju - huomaa, että tämä on kuin linkitetty luettelo impl threadpool = pool; // set thread pool - root of threadpool ........ threadId = ++ ThreadId; ...... // Käynnistä ketju thisThread = new Thread (tämä, nimi + inttid.toString ()); thisThread.start (); } 

Tällä luokalla on kaksi päämenetelmää. Yksi on looginen Voi käsitellä () jonka aloitti ThreadPool luokassa ja etenee sitten rekursiivisesti. Tämä tarkistaa, onko nykyinen ketju (nykyinen LankaKetju esimerkiksi) on vapaa käsittelemään tehtävää. Jos se jo käsittelee tehtävää, se kutsuu ketjun seuraavaa.

 public Boolen canHandle () {if (! busy) {// Jos ei varattu System.out.println ("Pystyy käsittelemään tätä tapahtumaa tunnuksessa id =" + threadId); // todistaa tapahtumasta, kokeile {condLock.lock (); condWait.signal (); // Signal the HandleRequest, joka odottaa tätä ajotavassa .................................... ..... palaa tosi; } ......................................... /// Muuten katso jos seuraava ketjun objekti on vapaa /// käsittelemään pyynnön palautuksen seuraavaksi.canHandle (); 

Huomaa, että HandlePyyntö on menetelmä LankaKetju jota kutsutaan Kierreajo () menetelmä ja odottaa signaalia voi käsitellä menetelmä. Huomaa myös, kuinka tehtävä käsitellään komentokuvion avulla.

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