《七天七并發(fā)》第一天互斥和內(nèi)存模型
-
并行并不只是說多核
- bit level 并行
- intstruction-level并行静檬。通過流水線炭懊、亂序執(zhí)行并级、猜測(cè)執(zhí)行達(dá)到
- data-level并行。并行對(duì)大量數(shù)據(jù)施加統(tǒng)一操作凛虽,如GPU
- task-level死遭。多處理器
-
并發(fā)問題產(chǎn)生原因:
- 內(nèi)存可見性問題。
- 指令亂序執(zhí)行凯旋。編譯器靜態(tài)優(yōu)化呀潭、JVM動(dòng)態(tài)優(yōu)化、硬件亂序來(lái)優(yōu)化性能至非。++i 解釋成多個(gè)指令钠署,加重了亂序的影響。
-
synchronization:執(zhí)行順序上(互斥)荒椭、可見性上的雙重保證谐鼎。即happens before的語(yǔ)義:
- Each action in a thread happens before every action in that thread that comes later in the program's order.
- An unlock on a monitor happens before every subsequent lock on that same monitor.
- A write to a volatile field happens before every subsequent read of that same volatile.
- A call to start() on a thread happens before any actions in the started thread.
- All actions in a thread happen before any other thread successfully returns from a join() on that thread.
只涉及一個(gè)變量的互斥場(chǎng)景,使用java.util.concurrent.aotmic包是好選擇趣惠。
Thread.join方法使得檢查線程是否已終止狸棍,并將其他線程對(duì)共享內(nèi)存的修改對(duì)當(dāng)前線程可見。
-
【哲學(xué)家就餐問題】https://zh.wikipedia.org/wiki/%E5%93%B2%E5%AD%A6%E5%AE%B6%E5%B0%B1%E9%A4%90%E9%97%AE%E9%A2%98
- 【資源分級(jí)解法】將爭(zhēng)用的資源編號(hào)味悄,獲取時(shí)編號(hào)從小到大草戈,釋放時(shí)編號(hào)從大到小。如果已獲取資源3侍瑟,5唐片,要去獲取資源2,那么必須先釋放資源5涨颜,3费韭。原理是:只有持有最大編號(hào)資源的一個(gè)哲學(xué)家可以進(jìn)餐,而他不會(huì)卡死庭瑰,因?yàn)樗阅塬@取最大編號(hào)資源星持,是因?yàn)樗栊【幪?hào)資源已經(jīng)被持有了。這樣就能保證至少有一個(gè)哲學(xué)家可以進(jìn)餐见擦。
- 【服務(wù)生解法】通過一個(gè)服務(wù)生(master thread)原子性打包分配所需資源钉汗,保證了資源獲取的串行。
- 【Chandy/Misra解法】將筷子湊成對(duì)(資源打包)鲤屡,餓的人將獲得一個(gè)換筷子券损痰,餓的人將券給有筷子的人,有筷子的人吃完了酒来,將筷子給剛才給券的人卢未。原理是,多個(gè)資源打包,資源獲取辽社、釋放都是原子性的伟墙。
- 哲學(xué)家思考時(shí)間越一致,比如都不思考滴铅,一直在爭(zhēng)用資源戳葵,越容易死鎖。哲學(xué)家就餐時(shí)間越長(zhǎng)汉匙,越容易死鎖拱烁。
-
【incorrectly synchronized】即【 data race】:
- there is a write of a variable by one thread,
- there is a read of the same variable by another thread and
- the write and read are not ordered by synchronization
-
內(nèi)置鎖限制:
- 進(jìn)入阻塞后無(wú)法中斷
- 無(wú)法設(shè)置超時(shí)
- 代碼塊只能在同一個(gè)方法中
java內(nèi)存模型
《The Java Memory Model》http://www.cs.umd.edu/~pugh/java/memoryModel/
界定了這些并發(fā)問題的界限,并給出解決方案噩翠。
final
int old memory model. synchronization was the only way to ensure that all threads see the value of a final field that was written by the constructor.
新的JMM中戏自,只要constructor過程中this不逸出,則能保證final字段被其他線程正確的看到伤锚,如果final字段指向一個(gè)數(shù)組或?qū)ο笊帽剩瑪?shù)組或?qū)ο螅材鼙WC其他線程看到構(gòu)造器結(jié)束那個(gè)點(diǎn)的最新的值屯援,而不是現(xiàn)在最新的值(因?yàn)闃?gòu)造完成后猛们,有可能先被其他線程更新了)。
volatile
Under the old memory model, accesses to volatile variables could not be reordered with each other, but they could be reordered with nonvolatile variable accesses. 這樣會(huì)導(dǎo)致volatile不能作為兩個(gè)線程之間的信號(hào)變量狞洋,因?yàn)榫€程B看到volatile的變量變更后阅懦,不能保證在線程A中排在該volatile變量之前的write能被看到。
于是volatile變量似乎有了鎖的作用:Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire.
In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.
因此volatile只能保證可見性問題徘铝,沒法保證互斥問題。
volatile很實(shí)用于開關(guān)場(chǎng)景惯吕,比如標(biāo)識(shí)一件事做完了惕它,比如初始化完成了,或者網(wǎng)絡(luò)請(qǐng)求處理完成废登。
"double-checked locking" problem
為了lazy init淹魄,某個(gè)field被使用時(shí)候才init。
class SomeClass {
private Resource resource = null;
public Resource getResource() {
if (resource == null) {
synchronized {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
因?yàn)榭赡茉赗esource構(gòu)造完成之前堡距,它的引用就被賦給SomeClass的field resource甲锡。所以線程B可能看到被初始化了一般的Resource對(duì)象。
The class loader, which synchronizes on the classes' Class object, executes static initializer blocks at class initialization time. That means that the effect of static initializers is automatically visible to all threads as soon as the class loads.
【解決方案一】:
static filed的初始化已經(jīng)基于這個(gè)class的class obj synchronizes過了羽戒,所以一旦初始化完成能理解被其他thread看到:
The class loader, which synchronizes on the classes' Class object, executes static initializer blocks at class initialization time. That means that the effect of static initializers is automatically visible to all threads as soon as the class loads.
基于這個(gè)缤沦,另外為了保證lazy load,可以這么做:
When the initialized object is a static field of a class with no other methods or fields, the JVM effectively performs lazy initialization automatically.
【解決方案二】:在JSR133標(biāo)準(zhǔn)實(shí)施后(>= jdk1.5)易稠,可以通過將resource申明為violate解決這個(gè)問題缸废。