JavaRush /Java Blog /Random-TL /Pamamahala ng pagkasumpungin
lexmirnov
Antas
Москва

Pamamahala ng pagkasumpungin

Nai-publish sa grupo

Mga Alituntunin sa Paggamit ng Mga Pabagu-bagong Variable

Ni Brian Goetz Hunyo 19, 2007 Orihinal: Pamamahala ng Volatility Volatile variable sa Java ay maaaring tawaging "synchronized-light"; Nangangailangan sila ng mas kaunting code na gagamitin kaysa sa mga naka-synchronize na bloke, kadalasang tumatakbo nang mas mabilis, ngunit maaari lamang gawin ang isang bahagi ng kung ano ang ginagawa ng mga naka-synchronize na bloke. Nagpapakita ang artikulong ito ng ilang pattern para sa epektibong paggamit ng pabagu-bago ng isip—at ilang babala tungkol sa kung saan hindi ito dapat gamitin. May dalawang pangunahing feature ang mga lock: mutual exclusion (mutex) at visibility. Nangangahulugan ang mutual exclusion na ang isang lock ay maaari lamang hawakan ng isang thread sa isang pagkakataon, at ang property na ito ay magagamit upang ipatupad ang mga protocol ng kontrol sa pag-access para sa mga nakabahaging mapagkukunan upang isang thread lang ang gagamit ng mga ito sa isang pagkakataon. Ang visibility ay isang mas banayad na isyu, ang layunin nito ay upang matiyak na ang mga pagbabagong ginawa sa mga pampublikong mapagkukunan bago ilabas ang lock ay makikita ng susunod na thread na kukuha sa lock na iyon. Kung hindi ginagarantiyahan ng pag-synchronize ang visibility, ang mga thread ay maaaring makatanggap ng mga lipas o hindi tamang value para sa mga pampublikong variable, na hahantong sa ilang malalang problema.
Mga variable na pabagu-bago
Ang mga pabagu-bagong variable ay may mga katangian ng visibility ng mga naka-synchronize, ngunit kulang ang kanilang atomicity. Nangangahulugan ito na awtomatikong gagamitin ng mga thread ang pinakabagong mga halaga ng pabagu-bago ng isip na mga variable. Magagamit ang mga ito para sa kaligtasan ng thread , ngunit sa napakalimitadong hanay ng mga kaso: yaong hindi nagpapakilala ng mga ugnayan sa pagitan ng maraming variable o sa pagitan ng kasalukuyan at hinaharap na mga halaga ng isang variable. Kaya, ang pabagu-bago ng isip lamang ay hindi sapat upang ipatupad ang isang counter, isang mutex, o anumang klase na ang mga hindi nababagong bahagi ay nauugnay sa maraming mga variable (halimbawa, "simula <=end"). Maaari kang pumili ng volatile lock para sa isa sa dalawang pangunahing dahilan: pagiging simple o scalability. Ang ilang mga konstruksyon ng wika ay mas madaling isulat bilang code ng programa, at sa ibang pagkakataon ay basahin at maunawaan, kapag gumagamit sila ng mga pabagu-bagong variable sa halip na mga kandado. Gayundin, hindi tulad ng mga kandado, hindi nila mai-block ang isang thread at samakatuwid ay hindi gaanong madaling kapitan ng mga isyu sa scalability. Sa mga sitwasyon kung saan mas marami ang mga reads kaysa writes, ang mga variable na pabagu-bago ng isip ay maaaring magbigay ng mga benepisyo sa pagganap sa mga lock.
Mga kondisyon para sa tamang paggamit ng pabagu-bago ng isip
Maaari mong palitan ang mga kandado ng mga pabagu-bago ng isip sa limitadong bilang ng mga pangyayari. Upang maging ligtas sa thread, dapat matugunan ang parehong pamantayan:
  1. Ang isinulat sa isang variable ay independiyente sa kasalukuyang halaga nito.
  2. Ang variable ay hindi lumalahok sa mga invariant sa iba pang mga variable.
Sa madaling salita, ang mga kundisyong ito ay nangangahulugan na ang mga wastong halaga na maaaring isulat sa isang pabagu-bagong variable ay independiyente sa anumang iba pang estado ng programa, kabilang ang kasalukuyang estado ng variable. Ang unang kundisyon ay hindi kasama ang paggamit ng mga pabagu-bagong variable bilang thread-safe na mga counter. Bagama't ang increment (x++) ay mukhang isang solong operasyon, ito ay talagang isang buong sequence ng read-modify-write na mga operasyon na dapat gawin nang atomically, na hindi naibibigay ng volatile. Ang isang wastong operasyon ay mangangailangan na ang halaga ng x ay mananatiling pareho sa buong operasyon, na hindi maaaring makamit gamit ang pabagu-bago ng isip. (Gayunpaman, kung masisiguro mong ang halaga ay isinulat mula lamang sa isang thread, maaaring tanggalin ang unang kundisyon.) Sa karamihan ng mga sitwasyon, ang una o pangalawang kundisyon ay lalabag, na ginagawang pabagu-bago ng isip ang mga variable na hindi gaanong ginagamit na diskarte sa pagkamit ng kaligtasan ng thread kaysa sa mga naka-synchronize. Ang listahan 1 ay nagpapakita ng isang hindi-thread-safe na klase na may hanay ng mga numero. Naglalaman ito ng invariant - ang lower bound ay palaging mas mababa o katumbas ng upper. @NotThreadSafe public class NumberRange { private int lower, upper; public int getLower() { return lower; } public int getUpper() { return upper; } public void setLower(int value) { if (value > upper) throw new IllegalArgumentException(...); lower = value; } public void setUpper(int value) { if (value < lower) throw new IllegalArgumentException(...); upper = value; } } Dahil limitado ang mga variable ng estado ng range sa ganitong paraan, hindi ito magiging sapat na gawing pabagu-bago ang lower at upper field para matiyak na ligtas ang thread; kakailanganin pa rin ang pag-synchronize. Kung hindi, sa malao't madali ikaw ay magiging malas at ang dalawang thread na gumaganap ng setLower() at setUpper() na may hindi naaangkop na mga halaga ay maaaring humantong sa hanay sa isang hindi pare-parehong estado. Halimbawa, kung ang inisyal na halaga ay (0, 5), ang thread A ay tumatawag sa setLower(4), at sa parehong oras ang thread B ay tumatawag sa setUpper(3), ang mga interleaved na operasyong ito ay magreresulta sa isang error, bagama't pareho silang papasa sa tseke na dapat protektahan ang invariant. Bilang resulta, ang saklaw ay magiging (4, 3) - mga maling halaga. Kailangan nating gawing atomic ang setLower() at setUpper() sa iba pang mga pagpapatakbo ng hanay - at hindi magagawa iyon ng paggawa ng mga field na pabagu-bago.
Mga Pagsasaalang-alang sa Pagganap
Ang unang dahilan para gumamit ng pabagu-bago ng isip ay ang pagiging simple. Sa ilang mga sitwasyon, ang paggamit ng naturang variable ay mas madali kaysa sa paggamit ng lock na nauugnay dito. Ang pangalawang dahilan ay ang pagganap, kung minsan ang pabagu-bago ng isip ay gagana nang mas mabilis kaysa sa mga kandado. Napakahirap gumawa ng mga tumpak, sumasaklaw sa lahat ng mga pahayag tulad ng "Ang X ay palaging mas mabilis kaysa sa Y," lalo na pagdating sa mga panloob na operasyon ng Java Virtual Machine. (Halimbawa, maaaring ilabas ng JVM ang lock nang buo sa ilang sitwasyon, na nagpapahirap sa pagtalakay sa mga gastos ng pabagu-bago ng isip laban sa pag-synchronize sa abstract na paraan). Gayunpaman, sa karamihan sa mga modernong arkitektura ng processor, ang halaga ng pagbabasa ay pabagu-bago ng isip ay hindi gaanong naiiba sa halaga ng pagbabasa ng mga regular na variable. Ang halaga ng pagsusulat pabagu-bago ng isip ay makabuluhang mas mataas kaysa sa pagsusulat ng mga regular na variable dahil sa memory fencing na kinakailangan para sa visibility, ngunit sa pangkalahatan ay mas mura kaysa sa pagtatakda ng mga lock.
Mga pattern para sa wastong paggamit ng volatile
Maraming mga eksperto sa concurrency ang may posibilidad na iwasan ang paggamit ng mga variable na pabagu-bago ng isip dahil mas mahirap gamitin nang tama ang mga ito kaysa sa mga lock. Gayunpaman, mayroong ilang mahusay na tinukoy na mga pattern na, kung susundin nang mabuti, ay maaaring magamit nang ligtas sa iba't ibang uri ng mga sitwasyon. Laging igalang ang mga limitasyon ng pabagu-bago ng isip - gumamit lamang ng mga pabagu-bago ng isip na independiyente sa anumang bagay sa programa, at ito ay dapat na pigilan ka sa pagpasok sa mapanganib na teritoryo na may ganitong mga pattern.
Pattern #1: Mga Flag ng Katayuan
Marahil ang canonical na paggamit ng mga nababagong variable ay mga simpleng boolean status flag na nagsasaad na may naganap na mahalagang isang beses na kaganapan sa lifecycle, gaya ng pagkumpleto ng pagsisimula o isang kahilingan sa pagsara. Maraming mga application ang may kasamang control construct ng form: "hanggang sa handa na tayong mag-shut down, patuloy na tumakbo" tulad ng ipinapakita sa Listahan 2: Malamang volatile boolean shutdownRequested; ... public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { // do stuff } } na ang shutdown() na paraan ay tatawagin mula sa isang lugar sa labas ng loop - sa isa pang thread - kaya kailangan ang pag-synchronize para matiyak ang tamang variable visibility shutdownRequested. (Maaari itong tawagan mula sa isang JMX listener, isang action listener sa isang GUI event thread, sa pamamagitan ng RMI, sa pamamagitan ng isang web service, atbp.). Gayunpaman, ang isang loop na may naka-synchronize na mga bloke ay magiging mas masahol kaysa sa isang loop na may pabagu-bago ng estado flag tulad ng sa Listahan 2. Dahil pabagu-bago ng isip ay ginagawang mas madali ang pagsulat ng code at ang bandila ng estado ay hindi nakasalalay sa anumang iba pang estado ng programa, ito ay isang halimbawa ng isang mabuting paggamit ng volatile. Ang katangian ng naturang mga flag ng katayuan ay karaniwang may isang paglipat ng estado; ang shutdownRequested flag ay napupunta mula sa false hanggang true, at pagkatapos ay magsasara ang program. Ang pattern na ito ay maaaring i-extend sa mga flag ng estado na maaaring magbago pabalik-balik, ngunit kung ang ikot ng paglipat (mula sa mali patungo sa totoo hanggang sa mali) ay nangyayari nang walang panlabas na interbensyon. Kung hindi, kailangan ang ilang uri ng mekanismo ng atomic transition, tulad ng mga atomic variable.
Pattern #2: Isang beses na secure na pag-publish
Ang mga error sa visibility na posible kapag walang pag-synchronize ay maaaring maging mas mahirap na isyu kapag nagsusulat ng mga object reference sa halip na mga primitive na halaga. Kung walang pag-synchronize, makikita mo ang kasalukuyang value para sa isang object reference na isinulat ng isa pang thread at makikita pa rin ang mga stale na value ng estado para sa object na iyon. (Ang banta na ito ay ang ugat ng problema sa kasumpa-sumpa na double-check lock, kung saan binabasa ang isang object reference nang walang pag-synchronize, at nanganganib mong makita ang aktwal na reference ngunit makakuha ng isang bagay na bahagyang nabuo sa pamamagitan nito.) Isang paraan upang ligtas na mag-publish ng isang object ay upang gumawa ng isang reference sa isang pabagu-bago ng isip na bagay. Ang listahan 3 ay nagpapakita ng isang halimbawa kung saan, sa panahon ng pagsisimula, ang isang background thread ay naglo-load ng ilang data mula sa database. Sinusuri ng ibang code na maaaring subukang gamitin ang data na ito upang makita kung na-publish na ito bago subukang gamitin ito. public class BackgroundFloobleLoader { public volatile Flooble theFlooble; public void initInBackground() { // делаем много всякого theFlooble = new Flooble(); // единственная запись в theFlooble } } public class SomeOtherClass { public void doWork() { while (true) { // чё-то там делаем... // используем theFolooble, но только если она готова if (floobleLoader.theFlooble != null) doSomething(floobleLoader.theFlooble); } } } Kung ang reference sa theFlooble ay hindi pabagu-bago, ang code sa doWork() ay nanganganib na makakita ng isang bahagyang constructed na Flooble kapag sinusubukang i-reference ang Flooble. Ang pangunahing kinakailangan para sa pattern na ito ay ang nai-publish na bagay ay dapat na alinman sa thread-safe o epektibong hindi nababago (epektibong hindi nababago ay nangangahulugan na ang estado nito ay hindi kailanman nagbabago pagkatapos itong mai-publish). Ang isang pabagu-bagong link ay maaaring matiyak na ang isang bagay ay nakikita sa kanyang nai-publish na anyo, ngunit kung ang estado ng bagay ay nagbabago pagkatapos ng pag-publish, ang karagdagang pag-synchronize ay kinakailangan.
Pattern #3: Mga Independiyenteng Obserbasyon
Ang isa pang simpleng halimbawa ng ligtas na paggamit ng pabagu-bago ng isip ay kapag ang mga obserbasyon ay pana-panahong "na-publish" para gamitin sa loob ng isang programa. Halimbawa, mayroong isang environmental sensor na nakikita ang kasalukuyang temperatura. Mababasa ng background thread ang sensor na ito bawat ilang segundo at mag-update ng pabagu-bagong variable na naglalaman ng kasalukuyang temperatura. Ang iba pang mga thread ay maaaring basahin ang variable na ito, alam na ang halaga sa loob nito ay palaging napapanahon. Ang isa pang gamit para sa pattern na ito ay ang pagkolekta ng mga istatistika tungkol sa programa. Ipinapakita ng listahan 4 kung paano maaalala ng mekanismo ng pagpapatunay ang pangalan ng huling naka-log in na user. Ang lastUser reference ay muling gagamitin upang i-post ang halaga para magamit ng natitirang bahagi ng programa. public class UserManager { public volatile String lastUser; public boolean authenticate(String user, String password) { boolean valid = passwordIsValid(user, password); if (valid) { User u = new User(); activeUsers.add(u); lastUser = user; } return valid; } } Ang pattern na ito ay lumalawak sa nauna; ang halaga ay nai-publish para magamit sa ibang lugar sa programa, ngunit ang publikasyon ay hindi isang beses na kaganapan, ngunit isang serye ng mga independyente. Ang pattern na ito ay nangangailangan na ang nai-publish na halaga ay epektibong hindi nababago - na ang estado nito ay hindi magbabago pagkatapos ma-publish. Dapat malaman ng code na gumagamit ng value na maaari itong magbago anumang oras.
Pattern #4: pattern na "volatile bean".
Ang pattern na "volatile bean" ay naaangkop sa mga frameworks na gumagamit ng JavaBeans bilang "glorified structs". Ang pattern na "volatile bean" ay gumagamit ng JavaBean bilang isang lalagyan para sa isang pangkat ng mga independiyenteng property na may mga getter at/o setter. Ang katwiran para sa pattern na "volatile bean" ay maraming mga frameworks ang nagbibigay ng mga container para sa mga nababagong data holder (gaya ng HttpSession), ngunit ang mga bagay na inilagay sa mga container na ito ay dapat na thread-safe. Sa pabagu-bago ng isip na pattern ng bean, ang lahat ng elemento ng data ng JavaBean ay pabagu-bago, at ang mga getter at setter ay dapat na walang halaga - hindi sila dapat maglaman ng anumang lohika maliban sa pagkuha o pagtatakda ng kaukulang property. Bukod pa rito, para sa mga miyembro ng data na mga object reference, ang nasabing mga object ay dapat na epektibong hindi nababago. (Ito ay hindi pinapayagan ang array reference field, dahil kapag ang isang array reference ay idineklara na pabagu-bago, ang reference lamang na iyon, at hindi ang mga elemento mismo, ang may volatile property.) Gaya ng anumang pabagu-bagong variable, maaaring walang mga invariant o paghihigpit na nauugnay sa mga katangian ng JavaBeans . Ang isang halimbawa ng isang JavaBean na isinulat gamit ang pattern na "volatile bean" ay ipinapakita sa Listahan 5: @ThreadSafe public class Person { private volatile String firstName; private volatile String lastName; private volatile int age; public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getAge() { return age; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setAge(int age) { this.age = age; } }
Mas kumplikadong pabagu-bago ng isip pattern
Ang mga pattern sa nakaraang seksyon ay sumasaklaw sa karamihan ng mga karaniwang kaso kung saan ang paggamit ng pabagu-bago ng isip ay makatwiran at halata. Ang seksyong ito ay tumitingin sa isang mas kumplikadong pattern kung saan ang pabagu-bago ng isip ay maaaring magbigay ng isang pagganap o scalability na benepisyo. Ang mga mas advanced na pabagu-bago ng isip na mga pattern ay maaaring maging lubhang marupok. Napakahalaga na ang iyong mga pagpapalagay ay maingat na naidokumento at ang mga pattern na ito ay mahigpit na naka-encapsulate, dahil kahit na ang pinakamaliit na pagbabago ay maaaring masira ang iyong code! Gayundin, dahil ang pangunahing dahilan para sa mas kumplikadong pabagu-bago ng mga kaso ng paggamit ay ang pagganap, tiyaking mayroon ka talagang malinaw na pangangailangan para sa nilalayong pakinabang sa pagganap bago gamitin ang mga ito. Ang mga pattern na ito ay mga kompromiso na nagsasakripisyo sa pagiging madaling mabasa o kadalian ng pagpapanatili para sa mga posibleng tagumpay sa pagganap - kung hindi mo kailangan ang pagpapabuti ng pagganap (o hindi mapapatunayan na kailangan mo ito sa isang mahigpit na programa sa pagsukat), malamang na ito ay isang masamang pakikitungo dahil iyon ibinibigay mo ang isang bagay na mahalaga at nakakakuha ka ng mas kaunting kapalit.
Pattern #5: Murang read-write lock
Sa ngayon, dapat mong malaman na ang pabagu-bago ng isip ay masyadong mahina upang ipatupad ang isang counter. Dahil ang ++x ay mahalagang pagbabawas ng tatlong operasyon (basahin, idugtong, iimbak), kung magkamali, mawawala ang na-update na halaga kung susubukang dagdagan ng maraming thread ang pabagu-bagong counter nang sabay-sabay. Gayunpaman, kung mas marami ang nabasa kaysa sa mga pagbabago, maaari mong pagsamahin ang intrinsic na pag-lock at pabagu-bago ng isip na mga variable upang bawasan ang kabuuang overhead ng path ng code. Ang listahan 6 ay nagpapakita ng isang thread-safe na counter na gumagamit ng naka-synchronize upang matiyak na ang pagpapatakbo ng pagtaas ay atomic, at gumagamit ng pabagu-bago ng isip upang matiyak na ang kasalukuyang resulta ay nakikita. Kung madalang ang mga pag-update, mapapabuti ng diskarteng ito ang pagganap dahil limitado ang mga gastos sa pagbabasa sa mga pabagu-bagong pagbabasa, na sa pangkalahatan ay mas mura kaysa sa pagkuha ng hindi sumasalungat na lock. @ThreadSafe public class CheesyCounter { // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this") private volatile int value; public int getValue() { return value; } public synchronized int increment() { return value++; } } Ang dahilan kung bakit ang pamamaraang ito ay tinatawag na "cheap read-write lock" ay dahil gumagamit ka ng iba't ibang mekanismo ng timing para sa pagbabasa at pagsusulat. Dahil nilalabag ng mga write operation sa kasong ito ang unang kundisyon ng paggamit ng pabagu-bago ng isip, hindi ka maaaring gumamit ng pabagu-bago ng isip upang maipatupad ang isang counter nang ligtas - dapat kang gumamit ng lock. Gayunpaman, maaari mong gamitin ang pabagu-bago ng isip upang gawing nakikita ang kasalukuyang halaga kapag nagbabasa, kaya gumamit ka ng lock para sa lahat ng pagpapatakbo ng pagbabago at pabagu-bago para sa mga read-only na operasyon. Kung ang isang lock ay nagbibigay-daan lamang sa isang thread sa isang pagkakataon upang ma-access ang isang halaga, ang pabagu-bago ng isip na pagbabasa ay nagbibigay-daan sa higit sa isa, kaya kapag gumamit ka ng pabagu-bago ng isip upang protektahan ang nabasa, makakakuha ka ng mas mataas na antas ng palitan kaysa kung gumamit ka ng lock sa lahat ng code: at nagbabasa at nagtala. Gayunpaman, magkaroon ng kamalayan sa kahinaan ng pattern na ito: na may dalawang nakikipagkumpitensyang mekanismo ng pag-synchronize, maaari itong maging napakakumplikado kung lalampas ka sa pinakapangunahing aplikasyon ng pattern na ito.
Buod
Ang mga pabagu-bagong variable ay isang mas simple ngunit mas mahinang paraan ng pag-synchronize kaysa sa pag-lock, na sa ilang mga kaso ay nagbibigay ng mas mahusay na pagganap o scalability kaysa sa intrinsic na pag-lock. Kung natutugunan mo ang mga kondisyon para sa ligtas na paggamit ng pabagu-bago ng isip - ang isang variable ay tunay na independiyente sa parehong iba pang mga variable at sa sarili nitong mga nakaraang halaga - kung minsan ay maaari mong pasimplehin ang code sa pamamagitan ng pagpapalit ng naka-synchronize sa volatile. Gayunpaman, kadalasang mas marupok ang code na gumagamit ng volatile kaysa sa code na gumagamit ng locking. Ang mga pattern na iminungkahi dito ay sumasaklaw sa mga pinakakaraniwang kaso kung saan ang pagkasumpungin ay isang makatwirang alternatibo sa pag-synchronize. Sa pamamagitan ng pagsunod sa mga pattern na ito - at pag-iingat na huwag itulak ang mga ito nang lampas sa kanilang sariling mga limitasyon - maaari mong ligtas na magamit ang pabagu-bago sa mga kaso kung saan nagbibigay ang mga ito ng mga benepisyo.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION