Arkitektura ng hardware ng memorya

Ang modernong arkitektura ng hardware ng memorya ay naiiba sa modelo ng panloob na memorya ng Java. Samakatuwid, kailangan mong maunawaan ang arkitektura ng hardware upang malaman kung paano gumagana ang modelo ng Java dito. Inilalarawan ng seksyong ito ang pangkalahatang arkitektura ng hardware ng memorya, at inilalarawan ng susunod na seksyon kung paano gumagana ang Java dito.

Narito ang isang pinasimple na diagram ng arkitektura ng hardware ng isang modernong computer:

Arkitektura ng hardware ng memorya

Sa modernong mundo, ang isang computer ay may 2 o higit pang mga processor at ito na ang pamantayan. Ang ilan sa mga processor na ito ay maaari ding magkaroon ng maraming core. Sa ganitong mga computer, posibleng magpatakbo ng maramihang mga thread sa parehong oras. Ang bawat core ng processor ay may kakayahang magsagawa ng isang thread sa anumang oras. Nangangahulugan ito na ang anumang Java application ay isang priori multi-threaded, at sa loob ng iyong programa, isang thread sa bawat core ng processor ay maaaring tumakbo nang sabay-sabay.

Ang core ng processor ay naglalaman ng isang hanay ng mga rehistro na naninirahan sa memorya nito (sa loob ng core). Ito ay gumaganap ng mga operasyon sa pagpaparehistro ng data nang mas mabilis kaysa sa data na naninirahan sa pangunahing memorya ng computer (RAM). Ito ay dahil mas mabilis ma-access ng processor ang mga register na ito.

Ang bawat CPU ay maaari ding magkaroon ng sarili nitong cache layer. Karamihan sa mga modernong processor ay mayroon nito. Maaaring ma-access ng processor ang cache nito nang mas mabilis kaysa sa pangunahing memorya, ngunit hindi kasing bilis ng mga panloob na rehistro nito. Ang halaga ng bilis ng pag-access sa cache ay humigit-kumulang sa pagitan ng mga bilis ng pag-access ng pangunahing memorya at mga panloob na rehistro.

Bukod dito, may lugar ang mga processor para magkaroon ng multi-level na cache. Ngunit hindi ito napakahalagang malaman upang maunawaan kung paano nakikipag-ugnayan ang modelo ng memorya ng Java sa memorya ng hardware. Mahalagang malaman na ang mga processor ay maaaring may ilang antas ng cache.

Ang anumang computer ay naglalaman din ng RAM (pangunahing lugar ng memorya) sa parehong paraan. Ang lahat ng mga core ay maaaring ma-access ang pangunahing memorya. Ang pangunahing lugar ng memorya ay karaniwang mas malaki kaysa sa memorya ng cache ng mga core ng processor.

Sa sandaling kailangan ng processor ng access sa pangunahing memorya, binabasa nito ang bahagi nito sa memorya ng cache nito. Maaari rin itong magbasa ng ilang data mula sa cache papunta sa mga panloob na rehistro nito at pagkatapos ay magsagawa ng mga operasyon sa mga ito. Kapag kailangan ng CPU na isulat ang resulta pabalik sa pangunahing memorya, i-flush nito ang data mula sa panloob na rehistro nito sa cache, at sa ilang mga punto, sa pangunahing memorya.

Ang data na nakaimbak sa cache ay karaniwang ibinabalik sa pangunahing memorya kapag ang processor ay kailangang mag-imbak ng iba pa sa cache. Ang cache ay may kakayahang i-clear ang memorya nito at magsulat ng data sa parehong oras. Hindi kailangang basahin o isulat ng processor ang buong cache tuwing may update. Karaniwan ang cache ay na-update sa maliit na mga bloke ng memorya, ang mga ito ay tinatawag na "linya ng cache". Ang isa o higit pang "mga linya ng cache" ay maaaring basahin sa memorya ng cache, at ang isa o higit pang mga linya ng cache ay maaaring ma-flush pabalik sa pangunahing memorya.

Pinagsasama ang Java memory model at memory hardware architecture

Tulad ng nabanggit na, ang modelo ng memorya ng Java at arkitektura ng hardware ng memorya ay iba. Ang arkitektura ng hardware ay hindi nakikilala sa pagitan ng mga stack ng thread at mga tambak. Sa hardware, ang thread stack at HEAP (heap) ay nasa pangunahing memorya.

Ang mga bahagi ng mga stack at thread na tambak ay maaaring minsan ay naroroon sa mga cache at panloob na mga rehistro ng CPU. Ito ay ipinapakita sa diagram:

thread stack at HEAP

Kapag ang mga bagay at variable ay maaaring maimbak sa iba't ibang bahagi ng memorya ng computer, maaaring lumitaw ang ilang mga problema. Narito ang dalawang pangunahing:

  • Visibility ng mga pagbabagong ginawa ng thread sa mga nakabahaging variable.
  • Kondisyon ng lahi kapag nagbabasa, nagsusuri at nagsusulat ng mga nakabahaging variable.

Ang parehong mga isyung ito ay ipapaliwanag sa ibaba.

Visibility ng Mga Nakabahaging Bagay

Kung ang dalawa o higit pang mga thread ay nagbabahagi ng isang bagay nang walang wastong paggamit ng pabagu-bagong deklarasyon o pag-synchronize, kung gayon ang mga pagbabago sa nakabahaging bagay na ginawa ng isang thread ay maaaring hindi makita ng iba pang mga thread.

Isipin na ang isang nakabahaging bagay ay unang naka-imbak sa pangunahing memorya. Binabasa ng thread na tumatakbo sa isang CPU ang nakabahaging object sa cache ng parehong CPU. Doon siya gumagawa ng mga pagbabago sa bagay. Hanggang ang cache ng CPU ay na-flush sa pangunahing memorya, ang binagong bersyon ng nakabahaging bagay ay hindi makikita ng mga thread na tumatakbo sa iba pang mga CPU. Kaya, ang bawat thread ay maaaring makakuha ng sarili nitong kopya ng shared object, bawat kopya ay nasa isang hiwalay na CPU cache.

Ang sumusunod na diagram ay naglalarawan ng balangkas ng sitwasyong ito. Kinokopya ng isang thread na tumatakbo sa kaliwang CPU ang nakabahaging object sa cache nito at binago ang value ng count sa 2. Ang pagbabagong ito ay hindi nakikita ng ibang mga thread na tumatakbo sa kanang CPU dahil hindi pa na-flush pabalik sa main memory ang update para mabilang.

Upang malutas ang problemang ito, maaari mong gamitin ang pabagu-bagong keyword kapag nagdedeklara ng variable. Maaari nitong tiyakin na ang isang naibigay na variable ay direktang binabasa mula sa pangunahing memorya at palaging isinusulat pabalik sa pangunahing memorya kapag na-update.

Kondisyon ng lahi

Kung dalawa o higit pang mga thread ang nagbabahagi ng parehong bagay at higit sa isang thread ang nag-a-update ng mga variable sa nakabahaging bagay na iyon, maaaring magkaroon ng kundisyon ng lahi.

Isipin na binabasa ng thread A ang variable na bilang ng nakabahaging bagay sa cache ng processor nito. Isipin din na ang thread B ay gumagawa ng parehong bagay, ngunit sa cache ng isa pang processor. Ngayon ang thread A ay nagdaragdag ng 1 sa halaga ng bilang, at ang thread B ay ganoon din ang ginagawa. Ngayon ang variable ay nadagdagan ng dalawang beses - hiwalay ng +1 sa cache ng bawat processor.

Kung ang mga increment na ito ay isinagawa nang sunud-sunod, ang count variable ay madodoble at isusulat pabalik sa pangunahing memorya (orihinal na halaga + 2).

Gayunpaman, dalawang pagdaragdag ang isinagawa nang sabay-sabay nang walang wastong pag-synchronize. Anuman ang sinusulat ng thread (A o B) ang na-update na bersyon ng count nito sa pangunahing memorya, ang bagong value ay magiging 1 lang sa orihinal na value, sa kabila ng dalawang pagtaas.

Ang diagram na ito ay naglalarawan ng paglitaw ng problema sa kondisyon ng lahi na inilarawan sa itaas:

Upang malutas ang problemang ito, maaari mong gamitin ang Java synchronized block. Tinitiyak ng naka-synchronize na block na isang thread lang ang makakapasok sa isang partikular na kritikal na seksyon ng code sa anumang partikular na oras.

Ginagarantiyahan din ng mga naka-synchronize na bloke na ang lahat ng mga variable na na-access sa loob ng naka-synchronize na bloke ay mababasa mula sa pangunahing memorya, at kapag ang thread ay lumabas sa naka-synchronize na bloke, ang lahat ng na-update na mga variable ay ibabalik sa pangunahing memorya, hindi alintana kung ang variable ay idineklara na pabagu-bago o Hindi.