Programmēšana

Java padoms 67: Slinks tūlītējs

Tas nebija tik sen, ka mūs saviļņoja izredzes, ka iebūvētā atmiņa būs 8 bitu mikrodatora lēcienā no 8 KB līdz 64 KB. Spriežot pēc arvien pieaugošajām, resursu alkstošajām lietojumprogrammām, kuras mēs tagad izmantojam, ir pārsteidzoši, ka kādam kādreiz ir izdevies uzrakstīt programmu, lai ietilptu šajā niecīgajā atmiņas apjomā. Kaut arī šajās dienās mums ir daudz vairāk atmiņas, no tām metodēm, kas izveidotas, lai darbotos tik stingros ierobežojumos, var gūt dažas vērtīgas mācības.

Turklāt Java programmēšana nav tikai apletu un lietojumprogrammu rakstīšana izvietošanai personālajos datoros un darbstacijās; Java ir guvusi spēcīgus panākumus arī iegulto sistēmu tirgū. Pašreizējām iegultajām sistēmām ir salīdzinoši maz atmiņas resursu un skaitļošanas jaudas, tāpēc daudzi no vecajiem jautājumiem, ar kuriem saskaras programmētāji, ir parādījušies Java izstrādātājiem, kas strādā ierīču jomā.

Šo faktoru līdzsvarošana ir aizraujoša dizaina problēma: ir svarīgi pieņemt faktu, ka neviens risinājums iegultā dizaina jomā nebūs ideāls. Tātad mums ir jāsaprot to paņēmienu veidi, kas būs noderīgi, lai sasniegtu nepieciešamo līdzsvaru, kas nepieciešams darbam izvietošanas platformas ierobežojumu ietvaros.

Viena no atmiņas saglabāšanas metodēm, ko Java programmētāji uzskata par noderīgu, ir slinks acumirklis. Ar slinku instantāciju programma atturas no noteiktu resursu radīšanas, līdz resurss vispirms ir vajadzīgs - atbrīvojot vērtīgu atmiņas vietu. Šajā padomā mēs aplūkojam slinkos instantiation paņēmienus Java klases ielādē un objektu izveidē, kā arī īpašos apsvērumus, kas nepieciešami Singleton modeļiem. Materiāls šajā padomā ir iegūts no mūsu grāmatas 9. nodaļas darba, Java praksē: efektīva Java dizaina stili un idiomas (skat. resursus).

Dedzīgs un slinks tūlītējs: piemērs

Ja esat pazīstams ar Netscape tīmekļa pārlūkprogrammu un esat izmantojis abas 3.x un 4.x versijas, neapšaubāmi esat pamanījis atšķirību Java izpildlaika ielādē. Ja, palaižot Netscape 3, paskatās uz uzplaiksnījuma ekrānu, ievērosiet, ka tas ielādē dažādus resursus, tostarp Java. Tomēr, palaižot Netscape 4.x, tas neielādē Java izpildlaiku - tas gaida, kamēr apmeklējat Web lapu, kurā ir ietverts tags. Šīs divas pieejas ilustrē dedzīgs acumirklis (ielādējiet to, ja tas ir nepieciešams) un slinks acumirklis (pagaidiet, līdz tas tiek pieprasīts, pirms to ielādējat, jo tas, iespējams, nekad nebūs vajadzīgs).

Abām pieejām ir trūkumi: no vienas puses, vienmēr resursa ielāde var iztērēt dārgo atmiņu, ja resurss šīs sesijas laikā netiek izmantots; no otras puses, ja tas nav ielādēts, jūs maksājat cenu, ņemot vērā ielādes laiku, kad resurss vispirms ir nepieciešams.

Apsveriet slinku tūlītēju sagatavošanu kā resursu saglabāšanas politiku

Slinks instantiation Java iedalās divās kategorijās:

  • Slinks klases iekraušana
  • Slinka objekta radīšana

Slinks klases iekraušana

Java izpildlaikā ir iebūvēts slinks ātrums stundām. Nodarbības tiek ielādētas atmiņā tikai tad, kad uz tām pirmo reizi atsaucas. (Vispirms tos var ielādēt arī no tīmekļa servera, izmantojot HTTP.)

MyUtils.classMethod (); // pirmais zvans uz statiskās klases metodi Vector v = new Vector (); // pirmais zvans operatoram jauns 

Slinkās klases ielāde ir svarīga Java izpildlaika vides iezīme, jo noteiktos apstākļos tā var samazināt atmiņas lietojumu. Piemēram, ja programmas daļa nekad netiek izpildīta sesijas laikā, klases, uz kurām atsaucas tikai šajā programmas daļā, nekad netiks ielādētas.

Slinka objekta radīšana

Slinka objekta izveide ir cieši saistīta ar slinku klases ielādi. Pirmo reizi lietojot jauno atslēgvārdu klases tipam, kas iepriekš nav ielādēts, Java izpildlaiks to ielādēs jūsu vietā. Slinku objektu izveide var daudz vairāk samazināt atmiņas izmantošanu nekā slinka klases ielāde.

Lai ieviestu slinka objekta radīšanas jēdzienu, apskatīsim vienkāršu koda piemēru, kur a Rāmis izmanto a MessageBox lai parādītu kļūdu ziņojumus:

publiskā klase MyFrame paplašina Frame {private MessageBox mb_ = new MessageBox (); // privāts palīgs, ko izmanto šī klase private void showMessage (String message) {// iestatiet ziņojuma tekstu mb_.setMessage (message); mb_.pack (); mb_.parādīt (); }} 

Iepriekš minētajā piemērā, kad MyFrame ir izveidots MessageBox tiek izveidots arī instances mb_. Rekursīvi tiek piemēroti vieni un tie paši noteikumi. Tātad visi instances mainīgie ir inicializēti vai piešķirti klasē MessageBoxkonstruktori tiek piešķirti arī pie kaudzes utt. Ja MyFrame netiek izmantots, lai sesijas laikā parādītu kļūdas ziņojumu, mēs nevajadzīgi iztērējam atmiņu.

Šajā diezgan vienkāršajā piemērā mēs patiesībā negūsim pārāk daudz ieguvumu. Bet, ja ņemat vērā sarežģītāku klasi, kurā tiek izmantotas daudzas citas klases, kuras savukārt rekursīvi izmanto un izstaro vairāk objektu, iespējamās atmiņas izmantošana ir acīmredzamāka.

Apsveriet slinku ātrināšanu kā politiku, lai samazinātu resursu prasības

Slinka pieeja iepriekšminētajam piemēram ir uzskaitīta zemāk, kur objekts mb_ tiek instantiated pēc pirmā zvana uz showMessage (). (Tas ir, tikai līdz brīdim, kad tas faktiski ir vajadzīgs programmai.)

publiskā gala klase MyFrame paplašina Frame {private MessageBox mb_; // null, implicit // privāts palīgs, ko izmanto šī klase private void showMessage (virknes ziņojums) {if (mb _ == null) // pirmais šīs metodes izsaukums mb_ = new MessageBox (); // iestatiet ziņojuma tekstu mb_.setMessage (ziņojums); mb_.pack (); mb_.parādīt (); }} 

Ja paskatās tuvāk showMessage (), jūs redzēsiet, ka vispirms mēs nosakām, vai eksemplāra mainīgais mb_ ir vienāds ar nulli. Tā kā mēs neesam inicializējuši mb_ deklarēšanas brīdī, Java izpildlaiks mums par to ir parūpējies. Tādējādi mēs varam droši turpināt, izveidojot MessageBox instancē. Visi turpmākie zvani uz showMessage () konstatēs, ka mb_ nav vienāds ar nulli, tāpēc izlaižot objekta izveidi un izmantojot esošo instanci.

Reāls piemērs

Apskatīsim reālistiskāku piemēru, kur slinkai momentānai var būt galvenā loma programmas izmantoto resursu apjoma samazināšanā.

Pieņemsim, ka klients mums ir licis uzrakstīt sistēmu, kas ļaus lietotājiem katalogizēt attēlus failu sistēmā un nodrošināt iespēju skatīt sīktēlus vai pilnīgus attēlus. Mūsu pirmais mēģinājums varētu būt klases uzrakstīšana, kas ielādē attēlu tā konstruktorā.

publiskā klase ImageFile {privātā virknes faila nosaukums_; privāts attēls image_; public ImageFile (virknes faila nosaukums) {filename_ = faila nosaukums; // ielādēt attēlu} public String getName () {return filename_;} public Image getImage () {return image_; }} 

Iepriekš minētajā piemērā ImageFile īsteno pārmērīgu pieeju Attēls objekts. Par labu šis dizains garantē, ka attēls būs pieejams uzreiz zvana laikā getImage (). Tomēr tas ne tikai varēja būt sāpīgi lēns (direktorija gadījumā, kurā ir daudz attēlu), bet arī šis dizains varētu iztukšot pieejamo atmiņu. Lai izvairītos no šīm iespējamām problēmām, mēs varam tirgot tūlītējas piekļuves veiktspējas priekšrocības, lai samazinātu atmiņas lietojumu. Kā jūs, iespējams, nojautāt, mēs to varam panākt, izmantojot slinku instantiāciju.

Šeit ir atjaunināts ImageFile klasei, izmantojot to pašu pieeju kā klasei MyFrame izdarīja ar to MessageBox instances mainīgais:

publiskā klase ImageFile {privātā virknes faila nosaukums_; privāts Attēla attēls_; // = null, netiešs publisks ImageFile (virknes faila nosaukums) {// glabā tikai faila nosaukumu filename_ = faila nosaukums; } public String getName () {return filename_;} public Image getImage () {if (image _ == null) {// pirmais zvans uz getImage () // ielādējiet attēlu ...} return image_; }} 

Šajā versijā faktiskais attēls tiek ielādēts tikai pēc pirmā zvana uz getImage (). Tātad, atkārtojot, šeit ir kompromiss, ka, lai samazinātu kopējo atmiņas lietojumu un palaišanas laiku, mēs maksājam cenu par attēla ielādi pirmo reizi, kad tas tiek pieprasīts - ieviešot veiktspējas trāpījumu tajā programmas izpildes brīdī. Šī ir vēl viena idioma, kas atspoguļo Starpniekserveris modeli kontekstā, kurā nepieciešama ierobežota atmiņas izmantošana.

Iepriekš ilustrētā slinka izpausmes politika ir piemērota mūsu piemēriem, taču vēlāk jūs redzēsiet, kā dizains ir jāmaina vairāku pavedienu kontekstā.

Slinks instantiation Singleton modeļiem Java

Tagad apskatīsim Singleton modeli. Šeit ir vispārīgā forma Java valodā:

publiskā klase Singleton {private Singleton () {} statiskā privātā Singleton instance_ = new Singleton (); statiskā publiskā Singletona instance () {atgriešanās instance_; } // publiskās metodes} 

Vispārējā versijā mēs deklarējām un inicializējām instance_ šādi:

statiskais galīgais Singletona gadījums_ = jauns Singletons (); 

Lasītāji, kas pārzina GoF (četru bandu, kas sarakstīja grāmatu, Singleton C ++ ieviešanu) Dizaina modeļi: atkārtoti lietojamas objektorientētas programmatūras elementi - Gamma, Helms, Džonsons un Vlisīds) var būt pārsteigti, ka mēs neaizkavējām instance_ līdz izsaukumam uz instance () metodi. Tādējādi, izmantojot slinku instantiāciju:

publiskā statiskā Singletona instance () {if (instance _ == null) // Slinks instantiation instance_ = new Singleton (); atgriešanās instance_; } 

Iepriekš minētais saraksts ir tiešs GoF sniegtā C ++ Singleton piemēra ports, un to bieži reklamē arī kā vispārēju Java versiju. Ja jūs jau esat iepazinies ar šo veidlapu un esat pārsteigts, ka mēs šādi neuzrādījām mūsu vispārīgo Singletonu, jūs būsiet vēl pārsteigts, uzzinot, ka Java tas ir pilnīgi nevajadzīgs! Šis ir izplatīts piemērs tam, kas var notikt, ja pārnesat kodu no vienas valodas uz citu, neņemot vērā attiecīgās izpildlaika vides.

Ierakstam GoF C ++ Singleton versija izmanto slinku instantiation, jo nav garantijas objektu statiskās inicializācijas secībai izpildes laikā. (Skatiet Scott Meyer Singleton, lai iegūtu alternatīvu pieeju C ++.) Java valodā mums nav jāuztraucas par šiem jautājumiem.

Slinka pieeja Singleton iegūšanai nav nepieciešama Java, jo veids, kā Java izpildlaiks apstrādā klases ielādi un statisko instances mainīgo inicializēšanu. Iepriekš mēs esam aprakstījuši, kā un kad klases tiek ielādētas. Klase ar tikai publiskām statiskām metodēm tiek ielādēta Java izpildlaika laikā, kad pirmo reizi izsaucat kādu no šīm metodēm; kas mūsu Singletona gadījumā ir

Singleton s = Singleton.instance (); 

Pirmais zvans uz Singleton.instance () programmā piespiež Java izpildlaiku ielādēt klasi Singletons. Kā lauks instance_ tiek deklarēts kā statisks, Java izpildlaiks to inicializēs pēc veiksmīgas klases ielādes. Tādējādi garantē, ka zvans uz Singleton.instance () atgriezīs pilnībā inicializētu Singletonu - iegūt attēlu?

Slinks ātrums: bīstams daudzšķiedru lietojumos

Slinka instantiation izmantošana konkrētam Singleton ir ne tikai nevajadzīga Java, bet tā ir pilnīgi bīstama vairāku pavedienu lietojumprogrammu kontekstā. Apsveriet slinko versiju Singleton.instance () metodi, kur divi vai vairāki atsevišķi pavedieni mēģina iegūt atsauci uz objektu, izmantojot instance (). Ja pēc veiksmīgas līnijas izpildes viena pavediens ir novērsts ja (instance _ == null), bet pirms rinda ir pabeigta instance_ = jauns Singletons (), vēl viens pavediens var ievadīt šo metodi arī ar instances_ joprojām == nulles - nejauki!

Šī scenārija rezultāts ir varbūtība, ka tiks izveidots viens vai vairāki Singleton objekti. Šīs ir lielas galvassāpes, kad jūsu Singleton klase, teiksim, izveido savienojumu ar datu bāzi vai attālo serveri. Vienkāršs šīs problēmas risinājums būtu izmantot sinhronizēto atslēgas vārdu, lai aizsargātu metodi no vairākiem pavedieniem, kas tajā vienlaikus ienāk:

sinhronizēta statiska publiska instance () {...} 

Tomēr šī pieeja ir mazliet smagnēja lielākajai daļai daudzšķiedru lietojumprogrammu, kas plaši izmanto Singleton klasi, tādējādi izraisot bloķēšanu vienlaikus zvaniem uz instance (). Starp citu, sinhronizētās metodes izsaukšana vienmēr ir daudz lēnāka nekā nesinhronizētās metodes izsaukšana. Tātad mums ir nepieciešama sinhronizācijas stratēģija, kas nerada nevajadzīgu bloķēšanu. Par laimi šāda stratēģija pastāv. Tas ir pazīstams kā vēlreiz pārbaudiet idiomu.

Divreiz pārbaudiet idiomu

Izmantojiet dubultās pārbaudes idiomu, lai aizsargātu metodes, izmantojot slinku instantiāciju. Lūk, kā to ieviest Java:

public static Singleton instance () {if (instance _ == null) // nevēlaties šeit bloķēt {// šeit varētu būt divi vai vairāki pavedieni !!! sinhronizēts (Singleton.class) {// jāpārbauda vēlreiz, jo viens no // bloķētajiem pavedieniem joprojām var ievadīt, ja (instance _ == null) instance_ = new Singleton (); // drošs}} atgriešanās instance_; } 

Divkāršās pārbaudes idioma uzlabo veiktspēju, izmantojot sinhronizāciju tikai tad, ja zvana vairāki pavedieni instance () pirms Singleton tiek uzbūvēts. Kad objekts ir instantizēts, instance_ vairs nav == nulle, ļaujot metodei izvairīties no vienlaicīgu zvanītāju bloķēšanas.

Vairāku pavedienu izmantošana Java var būt ļoti sarežģīta. Patiesībā vienlaicīguma tēma ir tik plaša, ka Doug Lea par to ir uzrakstījis veselu grāmatu: Vienlaicīga programmēšana Java. Ja vienlaicīgi plānojat programmēšanu, iesakām iegūt šīs grāmatas kopiju, pirms sākat rakstīt sarežģītas Java sistēmas, kas balstās uz vairākiem pavedieniem.

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