Ohjelmointi

JavaBeans: ominaisuudet, tapahtumat ja säikeiden turvallisuus

Java on dynaaminen kieli, joka sisältää helppokäyttöiset monisäikeiset kielirakenteet ja tukiluokat. Monet Java-ohjelmat käyttävät monisäikeisyyttä hyödyntääkseen sisäistä sovellusten rinnakkaisuutta, parantaakseen verkon suorituskykyä tai nopeuttaakseen käyttäjän palautetta. Useimmat Java-ajoajat käyttävät monisäikeisyyttä Java: n roskien keräysominaisuuden toteuttamiseen. Lopuksi, AWT luottaa toimintaansa varten myös erillisiin säikeisiin. Lyhyesti sanottuna yksinkertaisimmatkin Java-ohjelmat syntyvät aktiivisesti monisäikeisessä ympäristössä.

Siksi Java-pavut ovat myös käytössä tällaisessa dynaamisessa, monisäikeisessä ympäristössä, ja tässä on klassinen kohtaamisvaara. kilpailuolosuhteet. Kilpailuolosuhteet ovat ajoituksesta riippuvia ohjelmavirtaskenaarioita, jotka voivat johtaa valtion (ohjelmatiedot) vioittumiseen. Seuraavassa osassa esitän kaksi tällaista skenaariota. Jokainen Java-papu on suunniteltava kilpailuolosuhteet huomioon ottaen, jotta papu kestää useita samanaikaisia ​​asiakkaan ketjuja.

Monisäikeiset ongelmat yksinkertaisilla ominaisuuksilla

Bean-toteutusten on oletettava, että useat ketjut käyttävät ja / tai muokkaavat yhtä pavun esiintymää samanaikaisesti. Harkitse esimerkkinä väärin toteutetusta papusta (koska se liittyy monisäikeiseen tietoisuuteen) seuraava BrokenProperties-papu ja siihen liittyvä MTProperties-testiohjelma:

BrokenProperties.java

tuo java.awt.Point;

// Demo Bean, joka ei suojaa useiden lankojen käytöltä.

julkinen luokka BrokenProperties laajentaa pistettä {

// ------------------------------------------------ ------------------- // set () / get () 'Spot' -ominaisuudelle // --------------- -------------------------------------------------- -

public void setSpot (Point point) {// 'paikan' asettaja this.x = point.x; tämä.y = piste.y;

} public point getSpot () {// 'spot' getter palauttaa tämän; }} // Pavun loppu / luokan rikkoutuneet ominaisuudet

MTProperties.java

tuo java.awt.Point; tuoda apuohjelmia. *; tuoda apuohjelmia. pavut. *;

julkisen luokan MTP-ominaisuudet laajentavat säiettä {

suojattu BrokenProperties myBean; // kohdepapu bashiin ..

suojattu int myID; // jokaisessa säikeessä on vähän ID: tä

// ------------------------------------------------ ------------------- // main () pääsykohta // ---------------------- --------------------------------------------- julkinen staattinen void main ( Merkkijono [] argumentti) {

BrokenProperties papu; Langan lanka;

papu = (BrokenProperties) BeansKit.newBean ("BrokenProperties");

for (int i = 0; i <20; i ++) {// aloita 20 säiettä alapapulangalle = uudet MTPominaisuudet (papu, i); // ketjut saavat pääsyn papulankaan.start (); }} // ---------------------------------------------- --------------------- // MTProperties Constructor // ----------------------- --------------------------------------------

public MTProperties (BrokenProperties bean, int id) {this.myBean = papu; // huomaa papu osoittaaksesi tämän.myID = id; // huomaa kuka olemme} // ----------------------------------------- -------------------------- // langan pääsilmukka: // tee ikuisesti // luo uusi satunnainen piste x == y // käske papu ottamaan Point uudeksi 'spot' -ominaisuudeksi // kysy papulta, mikä sen 'spot' -ominaisuus on nyt asetettu // heittämään heiluttavaa, jos spot x ei ole sama kuin spot y // --------- -------------------------------------------------- -------- public void run () {int someInt; Pistepiste = uusi piste ();

while (tosi) {someInt = (int) (Math.random () * 100); piste.x = someInt; point.y = someInt; myBean.setSpot (piste);

piste = myBean.getSpot (); if (piste.x! = piste.y) {System.out.println ("papu vioittunut! x =" + piste.x + ", y =" + piste.y); System.exit (10); } System.out.print ((char) ('A' + myID)); System.out.flush (); }}} // Luokan MTP-ominaisuuksien loppu

Huomaa: apuohjelmapaketti, jonka on tuonut MTP-asunnot sisältää uudelleenkäytettäviä luokkia ja staattisia menetelmiä, jotka kirjailija on kehittänyt kirjaan.

Kaksi yllä olevaa lähdekoodiluetteloa määrittelevät pavun nimeltä BrokenProperties ja luokan MTP-asunnot, jota käytetään pavun harjoittamiseen 20 juoksevasta langasta. Seuraa meitä MTP-asunnot' main () lähtökohta: Ensin se saa aikaan BrokenProperties-papun, jonka jälkeen luodaan ja aloitetaan 20 säiettä. Luokka MTP-asunnot ulottuu java.lang.Thread, joten kaikki mitä meidän on tehtävä vaihtaaksemme luokkaa MTP-asunnot ketjuksi on ohittaa luokka Lankaon juosta() menetelmä. Ketjujen rakentajalla on kaksi argumenttia: papuobjekti, jonka kanssa säie kommunikoi, ja ainutlaatuinen tunniste, jonka avulla 20 säiettä voidaan erottaa helposti ajon aikana.

Tämän esittelyn loppu on meidän juosta() menetelmä luokassa MTP-asunnot. Tässä silmukoidaan ikuisesti luomalla satunnaisia ​​uusia (x, y) pisteitä, mutta seuraavalla ominaisuudella: niiden x-koordinaatti on aina yhtä suuri kuin y-koordinaatti. Nämä satunnaiset pisteet välitetään papuille setSpot () setterimenetelmä ja lue sitten heti takaisin getSpot () getter-menetelmä. Voit odottaa lukea paikalla ominaisuus on identtinen muutama millisekuntia sitten luotun satunnaispisteen kanssa. Tässä on esimerkkilähtö ohjelmasta, kun sitä kutsutaan komentorivillä:

ABBBBBBBBBBBBBBBBBBDJJJJJJJJJJJJJJJJJJJJEGHHHHHHHHHHHHHHHHHHSSSSSSSSSSSSSS IICCBBBBBBBBBBBBBBBBBKBDLJMBOPLQNRTPHHHHHHHHFFFFFFFFFFFFSSSSSSSSSSSSSSSSSS FFFFFFFFFFFFFFFAAAAAACCCCCCCKKKKKKKKKKKKKKKKKMMMMMMMMMMMMMMMMMMMMMMMMMMMDD JEOQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBTTTTTTTTTTTTTTTTLPPPPPPP PPPPGGHHHFFFFFFFFIIIIIIIIIIIIIISSSSSSSSSSSSSSSSSSSSACCCCCCCCCCCCCCCCCCCKMD QQQQQQNNNNNNNNNNNNNNNNRRRRRTRRHHHHHHHHHFFFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIII MMMJEEEEEEEEEEDDDDEEEEEEEEEOOOOOOOOOOOOOOOOOOOOOOOOOOOQNNNNNNNNBTLPLRGFFFF FFFFFFFFFIIAAAAAAAAAAAAAAAAASSSSSSSSSSSSSSSSSSKKKKKKKKKKKKKKKKCCCCCCCMMJAA AACBean vioittunut! x = 67, y = 13 OOOOOOOOOOOOOOOOOOOOO 

Tulos näyttää 20 ketjua, jotka kulkevat rinnakkain (niin pitkälle kuin ihmisen tarkkailija menee); kukin säike käyttää rakennuksen aikana saamiaan tunnuksia yhden kirjaimen tulostamiseen A että T, aakkosen 20 ensimmäistä kirjainta. Heti kun mikä tahansa lanka huomaa, että luettu takaisin paikalla ominaisuus ei ole x = y: n ohjelmoitujen ominaisuuksien mukainen, säie tulostaa "papu vioittunut" -sanoman ja keskeyttää kokeen.

Se, mitä näet, on papuissa olevan rodun tilan korruptoiva sivuvaikutus setSpot () koodi. Tässä on tämä menetelmä jälleen:

public void setSpot (Point point) {// 'paikan' asettaja this.x = point.x; tämä.y = piste.y; } 

Mikä voisi koskaan mennä pieleen niin yksinkertaisessa koodikoodissa? Kuvitella lanka A kutsumus setSpot () pisteargumentin ollessa yhtä suuri kuin (67,67). Jos nyt hidastamme maailmankaikkeuden kelloa, jotta voimme nähdä Java-virtuaalikoneen (JVM) suorittavan jokaisen Java-käskyn yksi kerrallaan, voimme kuvitella lanka A x-koordinaattikopion käskyn suorittaminen (tämä.x = piste.x;) ja sitten yhtäkkiä, lanka A käyttöjärjestelmä jäätyy ja lanka C on tarkoitus toimia jonkin aikaa. Edellisessä toimintatilassaan lanka C oli juuri luonut oman uuden satunnaispisteen (13,13), nimeltään setSpot () itsensä, ja sitten jäätyi tekemään tilaa lanka M, heti sen jälkeen, kun se oli asettanut x-koordinaatiksi 13. Joten jatkettiin lanka C nyt jatkaa ohjelmoitua logiikkaansa: asettamalla y arvoksi 13 ja tarkistamalla, onko pisteen ominaisuus sama (13, 13), mutta huomaa, että se on salaperäisesti muuttunut laittomaksi tilaksi (67, 13); x-koordinaatti on puolet sen tilasta lanka A oli asettamassa paikalla ja y-koordinaatti on puolet sen tilasta lanka Coli asettunutpaikalla . Lopputulos on, että BrokenProperties-papu pääsee sisäisesti epäjohdonmukaiseen tilaan: rikkoutuneeseen omaisuuteen.

Aina ei-atominen tietorakennetta (eli rakennetta, joka koostuu useammasta kuin yhdestä osasta) voidaan muokata useammalla kuin yhdellä säikeellä kerrallaan, sinun on suojattava rakenne lukolla. Java-ohjelmassa tämä tehdään synkronoitu avainsana.

Varoitus: Toisin kuin kaikki muut Java-tyypit, huomaa, että Java ei takaa sitä pitkä ja kaksinkertainen käsitellään atomisesti! Tämä johtuu siitä, että pitkä ja kaksinkertainen vaativat 64 bittiä, mikä on kaksinkertainen pituus moderneimpien CPU-arkkitehtuurien sanan pituudesta (32 bittiä). Sekä yksittäisten konesanojen lataaminen että tallentaminen ovat luonnostaan ​​atomisia operaatioita, mutta 64-bittisten entiteettien siirtäminen vaatii kahta tällaista siirtoa, ja Java ei suojaa niitä tavallisesta syystä: suorituskyvystä. (Jotkut suorittimet sallivat järjestelmäväylän lukitsemisen suorittamaan monisanaisia ​​siirtoja atomisesti, mutta tämä toiminto ei ole käytettävissä kaikilla suorittimilla, ja joka tapauksessa olisi uskomattoman kallista soveltaa kaikkiin pitkä tai kaksinkertainen manipuloinnit!) Joten vaikka omaisuus koostuu vain yhdestä pitkä tai yksittäinen kaksinkertainen, sinun tulee käyttää kaikkia lukitusvarotoimia suojataaksesi pitkät tai kaksinkertaiset tunkeutumisesi yhtäkkiä täysin vioittuneilta.

synkronoitu avainsana merkitsee koodilohkon atomivaiheena. Koodia ei voi "jakaa", koska kun toinen säike keskeyttää koodin mahdollisesti syöttääkseen itsensä uudelleen (siis termi paluukoodi; kaikkien Java-koodien tulisi palata takaisin). Ratkaisu BrokenProperties-papuihimme on triviaali: vain korvaa se setSpot () menetelmä tällä:

public void setSpot (pistepiste) {// 'spot' -setteri synkronoitu (tämä) {this.x = point.x; tämä.y = piste.y; }} 

Tai vaihtoehtoisesti tämän kanssa:

julkinen synkronoitu void setSpot (pistepiste) {// 'paikan' asettaja this.x = piste.x; tämä.y = piste.y; } 

Molemmat korvaukset ovat täysin samanlaisia, vaikka pidän parempana ensimmäisestä tyylistä, koska se osoittaa selkeämmin, mikä on synkronoitu avainsana on: synkronoitu lohko on aina linkitetty objektiin, joka lukitaan. Tekijä lukittu Tarkoitan, että JVM yrittää ensin hankkia lukon (eli yksinoikeuden) esineeseen (toisin sanoen saada yksinoikeuden siihen) tai odottaa, kunnes objekti vapautuu, jos se on lukittu toisella säikeellä. Lukitus takaa, että minkä tahansa esineen voi lukita (tai omistaa) vain yksi lanka kerrallaan.

Joten synkronoitu (tämä) syntaksi toistaa selvästi sisäisen mekanismin: Suluissa oleva argumentti on lukittava kohde (nykyinen objekti) ennen koodilohkon syöttämistä. Vaihtoehtoinen syntakse, jossa synkronoitu avainsanaa käytetään modifikaattorina metodin allekirjoituksessa, se on yksinkertaisesti lyhyt versio edellisestä.

Varoitus: Kun staattiset menetelmät on merkitty synkronoitu, ei ole Tämä objekti lukita; vain ilmentymämenetelmät liittyvät nykyiseen objektiin. Joten kun luokan menetelmät synkronoidaan, java.lang.luokka menetelmän luokkaa vastaavaa objektia käytetään sen sijaan lukitsemaan. Tällä lähestymistavalla on vakavia vaikutuksia suorituskykyyn, koska kokoelma luokan instansseja jakaa yhden liitetyn Luokka esine; milloin tahansa Luokka objekti lukitaan, kaikkia kyseisen luokan objekteja (olipa 3, 50 tai 1000!) estetään käyttämästä samaa staattista menetelmää. Tämän vuoksi kannattaa miettiä kahdesti ennen kuin käytät synkronointia staattisten menetelmien kanssa.

Käytännössä muista aina nimenomainen synkronoitu muoto, koska sen avulla voit "sumuttaa" pienimmän mahdollisen koodilohkon menetelmän sisällä. Lyhennemuoto "sumuttaa" koko menetelmän, joka suorituskykysyistä on usein ei mitä sinä haluat. Kun ketju on kirjoittanut atomikoodilohkon, mitään muuta ketjua ei tarvitse suorittaa minkä tahansa saman objektin synkronoitu koodi voi tehdä niin.

Kärki: Kun esineeseen on saatu lukko, niin kaikki kyseisen objektiluokan synkronoidusta koodista tulee atomi. Siksi, jos luokassasi on useampi kuin yksi tietorakenne, jota on käsiteltävä atomisesti, mutta nämä tietorakenteet ovat toisin riippumaton toisiinsa, voi syntyä toinen suorituskyvyn pullonkaula. Asiakkaat, jotka kutsuvat synkronoituja menetelmiä, jotka manipuloivat yhtä sisäistä tietorakennetta, estävät kaikki muut asiakkaat, jotka kutsuvat muita menetelmiä, jotka käsittelevät luokan muita atomitietorakenteita. Sinun tulisi selvästi välttää tällaisia ​​tilanteita jakamalla luokka pienempiin luokkiin, jotka käsittelevät vain yhtä atomirakennetta kerrallaan.

JVM toteuttaa synkronointiominaisuutensa luomalla jonoja säikeistä, jotka odottavat objektin lukituksen avaamista. Vaikka tämä strategia on loistava yhdistettyjen tietorakenteiden yhtenäisyyden suojaamisessa, se voi johtaa monisäikeisiin liikenneruuhkiin, kun vähemmän tehokas koodiosa on merkitty synkronoitu.

Siksi kiinnitä aina huomiota siihen, kuinka paljon koodia synkronoit: sen pitäisi olla ehdoton vähimmäismäärä. Kuvittele esimerkiksi setSpot () menetelmä koostui alun perin

public void setSpot (pistepiste) {// 'spot' setter log.println ("setSpot () kutsutaan" + this.toString ()); tämä.x = piste.x; tämä.y = piste.y; } 

vaikkakin println lausunto saattaa loogisesti kuulua setSpot () menetelmä, se ei ole osa lausejärjestystä, joka on ryhmiteltävä atomikokonaisuuteen. Siksi tässä tapauksessa oikea tapa käyttää synkronoitu avainsana olisi seuraava:

public void setSpot (pistepiste) {// 'spot' setter log.println ("setSpot () kutsutaan" + this.toString ()); synkronoitu (tämä) {this.x = point.x; tämä.y = piste.y; }} 

"Laiska" tapa ja lähestymistapa, jota sinun tulisi välttää, näyttää tältä:

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