Programmēšana

JavaBeans: īpašības, notikumi un pavedienu drošība

Java ir dinamiska valoda, kas ietver viegli lietojamas vairāku pavedienu valodas konstrukcijas un atbalsta klases. Daudzas Java programmas paļaujas uz daudzsavienojumu, lai izmantotu iekšējo lietojumprogrammu paralēlismu, uzlabotu tīkla darbību vai paātrinātu lietotāju atsauksmes. Lielākā daļa Java izpildes laiku izmanto daudzsavienojumu, lai ieviestu Java atkritumu savākšanas funkciju. Visbeidzot, AWT darbojas arī ar atsevišķiem pavedieniem. Īsāk sakot, pat visvienkāršākās Java programmas ir dzimušas aktīvi daudzsavienojumu vidē.

Tāpēc Java pupiņas tiek izvietotas arī tik dinamiskā, vairāku pavedienu vidē, un šeit slēpjas klasiskās briesmas sastapties sacensību apstākļi. Sacensību apstākļi ir no laika atkarīgi programmas plūsmas scenāriji, kas var izraisīt stāvokļa (programmas datu) korupciju. Nākamajā sadaļā es detalizēti aprakstīšu divus šādus scenārijus. Katra Java pupiņa jāveido, ņemot vērā sacensību apstākļus, lai pupiņa varētu izturēt vairāku klientu pavedienu vienlaicīgu lietošanu.

Vairāku pavedienu jautājumi ar vienkāršām īpašībām

Pupiņu ieviešanai ir jāpieņem, ka vairāki pavedieni vienlaikus piekļūst un / vai modificē vienu pupiņu instanci. Kā piemēru nepareizi ieviestai pupiņai (kas attiecas uz izpratni par daudzsavienojumu) ņemiet vērā šādu BrokenProperties pupu un ar to saistīto MTProperties testa programmu:

BrokenProperties.java

importēt java.awt.Point;

// Demo Bean, kas neaizsargā no vairāku pavedienu izmantošanas.

publiskā klase BrokenProperties pagarina punktu {

// ------------------------------------------------ ------------------- // iestatiet () / get () īpašumam “Spot” // --------------- -------------------------------------------------- -

public void setSpot (Point point) {// 'spot' iestatītājs this.x = point.x; this.y = punkts.y;

} public point getSpot () {// 'spot' getter atgriež šo; }} // Pupiņu beigas / klase BrokenProperties

MTProperties.java

importēt java.awt.Point; importa komunālie pakalpojumi. *; importa komunālie pakalpojumi. pupiņas. *;

publiskās klases MTProperties paplašina Thread {

aizsargāts BrokenProperties myBean; // mērķa pupiņa līdz bash ..

aizsargāts int myID; // katram pavedienam ir mazliet ID

// ------------------------------------------------ ------------------- // main () ievades punkts // ---------------------- --------------------------------------------- publiskais statiskais tukšums main ( Virkne [] argumenti) {

BrokenProperties pupas; Vītnes vītne;

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

for (int i = 0; i <20; i ++) {// sāciet 20 pavedienus līdz pupu pavedienam = jauni MTP īpašumi (pupas, i); // pavedieni iegūst piekļuvi pupiņu pavedienam.start (); }} // ---------------------------------------------- --------------------- // MTProperties konstruktors // ----------------------- --------------------------------------------

public MTProperties (BrokenProperties bean, int id) {this.myBean = pupas; // atzīmējiet pupiņu, lai to adresētu.myID = id; // atzīmējiet, kas mēs esam} // ----------------------------------------- -------------------------- // pavediena galvenā cilpa: // dariet uz visiem laikiem // izveidojiet jaunu nejaušu punktu ar x == y // saki pupiņai, lai pieņemtu punktu kā savu jauno “vietas” īpašumu // pajautā pupiņai, kādam tās “vietas” īpašumam tagad ir iestatīts // mest svārstību, ja plankums x nav vienāds ar punktu y // --------- -------------------------------------------------- -------- public void run () {int someInt; Punktu punkts = jauns Punkts ();

while (patiess) {someInt = (int) (Math.random () * 100); punkts.x = dažiInt; punkts.y = dažiInt; myBean.setSpot (punkts);

punkts = myBean.getSpot (); if (point.x! = point.y) {System.out.println ("Bean bojāts! x =" + point.x + ", y =" + point.y); System.exit (10); } System.out.print ((char) ('A' + myID)); System.out.flush (); }}} // MTP īpašību klases beigas

Piezīme. Pakalpojumu pakotni importēja MTP īpašumi satur atkārtoti izmantojamas klases un statiskas metodes, kuras autore ir izstrādājusi grāmatai.

Divos avota kodu sarakstos iepriekš ir definēta pupiņa ar nosaukumu BrokenProperties un klase MTP īpašumi, ko izmanto, lai vingrinātu pupiņu no 20 darbojošiem pavedieniem. Ļaujiet mums sekot MTP īpašumi' galvenais () ieejas punkts: Vispirms tas rada BrokenProperties pupiņu, kam seko 20 pavedienu izveide un sākšana. Klase MTP īpašumi pagarina java.lang.Thread, tāpēc viss, kas mums jādara, lai pārvērstu klasi MTP īpašumi pavedienā ir klases ignorēšana Vītne's palaist () metodi. Mūsu pavedienu konstruktoram ir divi argumenti: pupiņu objekts, ar kuru pavediens sazināsies, un unikāla identifikācija, kas ļauj 20 pavedienus izpildes laikā viegli atšķirt.

Šī demo biznesa beigas ir mūsu palaist () metode klasē MTP īpašumi. Šeit mēs uz visiem laikiem izveidojam cilpu, izveidojot nejaušus jaunus (x, y) punktus, bet ar šādu raksturlielumu: to x koordināta vienmēr ir vienāda ar viņu y koordinātu. Šie nejaušie punkti tiek nodoti pupiņām setSpot () iestatītāja metodi un pēc tam nekavējoties nolasiet, izmantojot getSpot () getter metode. Jūs varētu sagaidīt lasījumu plankums īpašība ir identiska nejaušajam punktam, kas izveidots pirms dažām milisekundēm. Šeit ir parauga programma, ko izsauc komandrindā:

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

Izvade parāda 20 pavedienus, kas darbojas paralēli (ciktāl to novēro cilvēks); katrs pavediens izmanto ID, ko tā saņēma būvniecības laikā, lai drukātu vienu no burtiem A uz T, pirmie 20 alfabēta burti. Tiklīdz kāds pavediens atklāj, ka tas ir lasīts atpakaļ plankums rekvizīts neatbilst ieprogrammētajai x = y īpašībai, pavediens izdrukā ziņojumu "Bean bojāts" un pārtrauc eksperimentu.

Tas, ko jūs redzat, ir valsts korumpējoša blakusefekts rases apstākļiem pupiņu iekšienē setSpot () kods. Šeit ir šī metode vēlreiz:

public void setSpot (Point point) {// 'spot' iestatītājs this.x = point.x; this.y = punkts.y; } 

Kas vispār varētu noiet greizi tik vienkāršā koda kodā? Iedomājieties pavediens A zvana setSpot () ar punktu argumentu, kas vienāds ar (67,67). Ja tagad mēs palēnināsim Visuma pulksteni, lai mēs varētu redzēt, kā Java virtuālā mašīna (JVM) izpilda katru Java paziņojumu pa vienam, mēs varam iedomāties pavediens A izpildot x koordinātu kopijas paziņojumu (this.x = punkts.x;) un tad pēkšņi pavediens A kļūst iesaldēta ar operētājsistēmu, un vītne C ir plānots kādu laiku darboties. Iepriekšējā darbības stāvoklī vītne C tikko bija izveidojis savu jauno nejaušo punktu (13,13), sauktu setSpot () pati, un pēc tam sasalusi, lai atbrīvotu vietu vītne M, uzreiz pēc tam, kad tā bija iestatījusi x koordinātu uz 13. Tātad, atsāka vītne C tagad turpina savu ieprogrammēto loģiku: iestatot y uz 13 un pārbaudot, vai vietas īpašums ir vienāds (13, 13), bet konstatē, ka tas ir noslēpumaini mainījies uz nelegālu stāvokli (67, 13); x koordināta ir puse no stāvokļa pavediens A bija iestatījums plankums līdz, un y koordināta ir puse no stāvokļa vītne Cbija iestatījisplankums uz. Rezultāts ir tāds, ka BrokenProperties pupai beidzas iekšēji nekonsekvents stāvoklis: salauzts īpašums.

Ikreiz, kad ne atomu datu struktūru (tas ir, struktūru, kas sastāv no vairāk nekā vienas daļas) vienlaikus var modificēt ar vairāk nekā vienu pavedienu, jums ir jāaizsargā struktūra, izmantojot slēdzeni. Java valodā tas tiek darīts, izmantojot sinhronizēts atslēgvārds.

Brīdinājums: Atšķirībā no visiem citiem Java veidiem, ņemiet vērā, ka Java to negarantē ilgi un dubultā izturas atomiski! Tas ir tāpēc, ka ilgi un dubultā nepieciešami 64 biti, kas ir divreiz lielāks par mūsdienu CPU arhitektūras vārdu garumu (32 biti). Gan vienas mašīnvārda ielāde, gan glabāšana pēc būtības ir atomu darbības, taču 64 bitu entītiju pārvietošanai nepieciešami divi šādi pārvietojumi, un Java tos neaizsargā parastā iemesla dēļ: veiktspējas dēļ. (Daži centrālie procesori ļauj bloķēt sistēmas kopni, lai veiktu vairāku vārdu pārsūtīšanu, taču šī iespēja nav pieejama visiem procesoriem, un jebkurā gadījumā to būtu neticami dārgi izmantot visiem ilgi vai dubultā manipulācijas!) Tātad, pat ja īpašums sastāv tikai no viena ilgi vai viens dubultā, jums vajadzētu izmantot visus piesardzības pasākumus bloķēšanai, lai pasargātu garos vai dubultos esošos no pēkšņas pilnīgas sabojāšanas.

The sinhronizēts atslēgvārds koda bloku atzīmē kā atomu soli. Kodu nevar "sadalīt", tāpat kā tad, kad cits pavediens pārtrauc kodu, lai potenciāli atkārtoti ievadītu šo bloku (līdz ar to termins reentrant kods; visiem Java kodiem vajadzētu būt atkārtotiem). Mūsu BrokenProperties pupiņu risinājums ir niecīgs: vienkārši nomainiet to setSpot () metodi ar šo:

public void setSpot (Point point) {// 'spot' seter synchronized (this) {this.x = point.x; this.y = punkts.y; }} 

Vai arī ar šo:

publiski sinhronizēts void setSpot (Point point) {// 'spot' iestatītājs this.x = point.x; this.y = punkts.y; } 

Abi aizstājēji ir pilnīgi līdzvērtīgi, lai gan es dodu priekšroku pirmajam stilam, jo ​​tas skaidrāk parāda, kāda ir precīzā funkcija sinhronizēts atslēgvārds ir: sinhronizēts bloks ir vienmēr saistīts ar objektu, kas tiek bloķēts. Autors aizslēgts Es domāju, ka JVM vispirms mēģina iegūt objekta bloķēšanu (tas ir, ekskluzīvu piekļuvi) (tas ir, iegūt ekskluzīvu piekļuvi tam), vai arī gaida, kamēr objekts tiek atbloķēts, ja to ir bloķējis cits pavediens. Bloķēšanas process garantē, ka jebkuru objektu vienlaikus var bloķēt (vai piederēt tikai viens pavediens).

Tātad, sinhronizēts (šis) sintakse skaidri atkārto iekšējo mehānismu: Iekavās esošais arguments ir bloķējamais objekts (pašreizējais objekts) pirms koda bloka ievadīšanas. Alternatīvā sintakse, kur sinhronizēts atslēgvārds tiek izmantots kā modifikators metodes parakstā, tas ir vienkārši pirmā stenogrāfiskā versija.

Brīdinājums: Kad tiek atzīmētas statiskās metodes sinhronizēts, tur nav šo objekts bloķēšanai; ar pašreizējo objektu ir saistītas tikai instances metodes. Tātad, kad klases metodes ir sinhronizētas, java.lang.Class objekts, kas atbilst metodes klasei, tiek izmantots bloķēšanai. Šai pieejai ir nopietna ietekme uz veiktspēju, jo klases instanču kopumam ir viens saistīts Klase objekts; kad vien tas Klase objekts tiek bloķēts, visiem šīs klases objektiem (neatkarīgi no tā, vai tie ir 3, 50 vai 1000!) ir aizliegts izmantot to pašu statisko metodi. Paturot to prātā, pirms sinhronizācijas izmantošanas ar statiskām metodēm vajadzētu padomāt divreiz.

Praksē vienmēr atcerieties skaidru sinhronizēto formu, jo tā ļauj "atomizēt" pēc iespējas mazāku koda bloku metodē. Stenogrāfijas forma "atomizē" visu metodi, kas veiktspējas apsvērumu dēļ bieži ir ko tu gribi. Kad pavediens ir ievadījis koda atomu bloku, nav neviena cita pavediena, kas jāizpilda jebkurš sinhronizēts kods uz tā paša objekta var to izdarīt.

Padoms: Kad objektam tiek iegūta bloķēšana, tad visi šī objekta klases sinhronizētais kods kļūs par atomu. Tādēļ, ja jūsu klasē ir vairāk nekā viena datu struktūra, kas jāapstrādā atomu veidā, bet šīs datu struktūras ir citādi neatkarīgs viens no otra, tad var rasties vēl viena veiktspējas vājā vieta. Klienti, kas izsauc sinhronizētas metodes, kas manipulē ar vienu iekšējo datu struktūru, bloķēs visus pārējos klientus, kuri izsauc citas metodes, kas nodarbojas ar citām jūsu klases atomu datu struktūrām. Skaidrs, ka jums vajadzētu izvairīties no šādām situācijām, sadalot klasi mazākās klasēs, kas vienlaikus apstrādā tikai vienu datu struktūru, kas vienlaikus jāapstrādā.

JVM īsteno sinhronizācijas funkciju, izveidojot pavedienu rindas, kas gaida objekta atbloķēšanu. Lai gan šī stratēģija ir lieliska, ja jāaizsargā salikto datu struktūru konsekvence, tā var izraisīt daudzšķiedru satiksmes sastrēgumus, ja koda sadaļa, kas nav tik efektīva, tiek atzīmēta kā sinhronizēts.

Tāpēc vienmēr pievērsiet uzmanību tam, cik daudz koda jūs sinhronizējat: tam jābūt absolūti nepieciešamajam minimumam. Piemēram, iedomājieties mūsu setSpot () metode sākotnēji sastāvēja no:

public void setSpot (Point point) {// 'spot' seteris log.println ("setSpot () izsaukts" + this.toString ()); this.x = punkts.x; this.y = punkts.y; } 

Lai gan println paziņojums varētu loģiski pieder pie setSpot () metodi, tā nav apgalvojumu secības daļa, kas jāgrupē atomu kopumā. Tāpēc šajā gadījumā pareizais veids, kā izmantot sinhronizēts atslēgvārds būtu šāds:

public void setSpot (Point point) {// 'spot' seteris log.println ("setSpot () izsaukts" + this.toString ()); sinhronizēts (tas) {this.x = point.x; this.y = punkts.y; }} 

"Slinkais" veids un pieeja, no kuras jums vajadzētu izvairīties, izskatās šādi:

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