java内存块_JVM上的并发和Java内存模型之同步块笔记
本文來(lái)自 圖靈社區(qū)@fairjm
轉(zhuǎn)截請(qǐng)注明出處
這個(gè)是書(shū)的筆記 書(shū)可以在safaribooksonline看,話(huà)說(shuō)這個(gè)真的是一個(gè)很好的讀外文書(shū)的網(wǎng)站啊,一個(gè)月39刀就可以暢想全站的書(shū),很值。(訂訂訂訂訂
因?yàn)槭枪P記所以覺(jué)得散不是你的錯(cuò)覺(jué)...因?yàn)楸緛?lái)就是散的...
筆記記錄了一些概念 方便復(fù)習(xí)回顧的時(shí)候看 更多內(nèi)容可以戳上面的鏈接
Scala的內(nèi)存模型 多線(xiàn)程能力 和它的線(xiàn)程間同步全部繼承自JVM
所有的抽象都有一定程度的泄漏 (all abstractions are to some extent leaky)
Processes and Threads
這通常是OS的任務(wù):指派程序的執(zhí)行部分給特定的處理器 - 這個(gè)機(jī)制被稱(chēng)為多任務(wù),并且這對(duì)于計(jì)算機(jī)用戶(hù)來(lái)說(shuō)是透明的。
時(shí)間片(time slices)
一個(gè)進(jìn)程是正被執(zhí)行的計(jì)算機(jī)程序的實(shí)例。兩個(gè)進(jìn)程不能直接讀取彼此的內(nèi)存或者同時(shí)使用大部分的資源,使用多進(jìn)程來(lái)表述多任務(wù)會(huì)非常麻煩 。
在同一個(gè)進(jìn)程中互相獨(dú)立的運(yùn)算單元被稱(chēng)為線(xiàn)程。在典型的操作系統(tǒng)中,線(xiàn)程的數(shù)量比進(jìn)程多得多。
每一個(gè)線(xiàn)程在程序運(yùn)行時(shí)描述了當(dāng)前的狀態(tài):通過(guò)程序棧和程序計(jì)數(shù)器。
當(dāng)我們說(shuō)程序執(zhí)行了一個(gè)動(dòng)作(比如向內(nèi)存寫(xiě)入內(nèi)容) 我們的意思是一個(gè)處理器執(zhí)行了執(zhí)行這個(gè)動(dòng)作的線(xiàn)程。(注意這邊的分句,我自己從onenote上粘貼上來(lái)都看了好幾遍...)
OS線(xiàn)程是操作系統(tǒng)提供的編程設(shè)置 通常通過(guò)OS相關(guān)的編程接口來(lái)暴露出來(lái)被使用
在同一個(gè)進(jìn)程中的分隔的線(xiàn)程共享同一區(qū)域的內(nèi)存,通過(guò)讀寫(xiě)內(nèi)存來(lái)彼此交互。另一個(gè)來(lái)定義進(jìn)程的方式是: 一系列的OS線(xiàn)程和這些線(xiàn)程共享的內(nèi)存和資源。
系統(tǒng)周期性地指派不同的OS線(xiàn)程到CPU核心中來(lái)允許計(jì)算在所有處理器中進(jìn)行。
java.lang.Thread Java的線(xiàn)程是直接映射到系統(tǒng)線(xiàn)程的 這意味著Java線(xiàn)程的行為是和OS線(xiàn)程是非常相似的
創(chuàng)建和啟動(dòng)線(xiàn)程
當(dāng)JVM進(jìn)程啟動(dòng)時(shí) 他默認(rèn)會(huì)創(chuàng)建一些線(xiàn)程。最重要的線(xiàn)程是主線(xiàn)程(main thread) 執(zhí)行程序的 main方法。這個(gè)線(xiàn)程的名字就叫main。
Thread的狀態(tài):
剛被創(chuàng)建時(shí)是 new
當(dāng)被執(zhí)行時(shí)是 runnable
結(jié)束執(zhí)行是 terminated
啟動(dòng)一個(gè)獨(dú)立的線(xiàn)程包含兩步:
1 創(chuàng)建線(xiàn)程對(duì)象
2 用start方法執(zhí)行
object ThreadsCreation extends App {
class MyThread extends Thread {
override def run(): Unit = {
println("New thread running.")
}
}
val t = new MyThread
t.start()
t.join()
println("New thread joined.")
}
join方法是中止main線(xiàn)程的運(yùn)行直到t線(xiàn)程運(yùn)行完畢
執(zhí)行這個(gè)方法 將main線(xiàn)程轉(zhuǎn)換到了 waiting 狀態(tài)
等待的線(xiàn)程放棄了他的執(zhí)行機(jī)會(huì) OS可以將處理器用于其他的線(xiàn)程
注意: 等待的線(xiàn)程提醒OS它們正在等待某個(gè)狀態(tài)并且 停止消耗 CPU周期 而不是重復(fù)地檢查這個(gè)狀態(tài)
sleep方法將線(xiàn)程放入 timed waiting 狀態(tài)
OS同樣可以將本該運(yùn)行這個(gè)線(xiàn)程的處理器用來(lái)運(yùn)行其他的線(xiàn)程
確定性: 特定的輸入 程序總會(huì)產(chǎn)生相同的輸出 而不管OS選擇的執(zhí)行調(diào)度有什么不同
由OS選擇的調(diào)度不同而會(huì)對(duì)相同輸出產(chǎn)生不同結(jié)果的稱(chēng)為 不確定性
大多數(shù)的多線(xiàn)程程序是非確定性的 這也是為什么多線(xiàn)程編程如此困難的原因
原子性執(zhí)行(Atomic execution)
競(jìng)態(tài)條件(race condition) 是一種現(xiàn)象:并發(fā)程序的輸出依賴(lài)于語(yǔ)句的執(zhí)行調(diào)度。
不一定是不正確的行為。
但如果一些調(diào)度的結(jié)果是我們不預(yù)期的 那么這個(gè)競(jìng)態(tài)條件就可以考慮是一個(gè)程序錯(cuò)誤
原子性執(zhí)行:代碼塊的原子執(zhí)行意味著代碼中的語(yǔ)句不能由執(zhí)行這段代碼的線(xiàn)程和另一個(gè)執(zhí)行這段代碼的線(xiàn)程交錯(cuò)執(zhí)行。
在原子性執(zhí)行中 要么所要執(zhí)行的代碼都執(zhí)行了 要么都沒(méi)執(zhí)行
可以通過(guò)同步塊保證原子性:
def getUniqueId() = this.synchronized {
val freshUid = uidCount + 1
uidCount = freshUid
freshUid
}
在錯(cuò)誤的對(duì)象上同步會(huì)造成比較難以找到的并發(fā)錯(cuò)誤。
每一個(gè)JVM中創(chuàng)建的對(duì)象都會(huì)有一個(gè)特殊的實(shí)體成為內(nèi)部鎖(intrinsic lock)或者成為監(jiān)視器(monitor) 用來(lái)保證只有一個(gè)線(xiàn)程可以執(zhí)行在這個(gè)對(duì)象上的同步塊
當(dāng)T線(xiàn)程執(zhí)行在x對(duì)象的同步塊時(shí) 我們可以說(shuō) T線(xiàn)程獲得了x的監(jiān)視器的所有權(quán)。當(dāng)線(xiàn)程執(zhí)行完這個(gè)同步塊 我們說(shuō)他釋放了這個(gè)監(jiān)視器。
同步塊語(yǔ)句是線(xiàn)程內(nèi)通信的基本機(jī)制。無(wú)論何時(shí),當(dāng)多個(gè)線(xiàn)程要訪(fǎng)問(wèn)并修改在同一個(gè)對(duì)象中的字段時(shí) 你應(yīng)該使用同步塊。
重排序(reordering)
同步塊也不是沒(méi)有開(kāi)銷(xiāo)的
對(duì)字段進(jìn)行寫(xiě)入將更加昂貴
同步塊的性能損失程度取決于JVM實(shí)現(xiàn) 但通常不會(huì)很大
你可能會(huì)趨向于避免使用同步塊當(dāng)你覺(jué)得這里沒(méi)有什么不好的語(yǔ)句交互執(zhí)行的情況,比如上面那個(gè)例子一樣(就是上面的代碼沒(méi)加同步塊被多個(gè)線(xiàn)程訪(fǎng)問(wèn))。永遠(yuǎn)不要這么做!(永遠(yuǎn)不要高估自己)
object ThreadSharedStateAccessReordering extends App {
for (i
var a = false
var b = false
var x = -1
var y = -1
val t1 = thread {
a = true
y = if (b) 0 else 1
}
val t2 = thread {
b = true
x = if (a) 0 else 1
}
t1.join()
t2.join()
assert(!(x == 1 && y == 1), s"x = $x, y = $y")
}
}
比如這個(gè)例子,我們的預(yù)期是0 0,10,0 1。1 1是我們所不預(yù)期的。
理論上不管我們運(yùn)行多少次 永遠(yuǎn)不會(huì)有 x=1 y=1的情況發(fā)生(所以assert不會(huì)不成立 也就不會(huì)拋錯(cuò))
但實(shí)際運(yùn)行就會(huì)..
JVM允許重排序由一個(gè)線(xiàn)程執(zhí)行的特定程序的語(yǔ)句 只要這個(gè)重排序不會(huì)改變這個(gè)線(xiàn)程的序列化執(zhí)行語(yǔ)義。(the JVM is allowed to reorder certain program statements executed by one thread as long as it does not change the serial semantics of the program for that particular thread)
PS 這里簡(jiǎn)單說(shuō)下:
a = true
y = if (b) 0 else 1
和
y = if (b) 0 else 1
a = true
對(duì)于序列化執(zhí)行來(lái)說(shuō),這兩個(gè)并沒(méi)什么不同(不會(huì)產(chǎn)生不同的結(jié)果),所以這兩個(gè)語(yǔ)句是可以重排序(不需要按照指令的執(zhí)行順序)
因?yàn)橐恍┨幚砥鞑豢偸菚?huì)按照程序的指令順序執(zhí)行
而且 線(xiàn)程也不需要將他們做的改動(dòng)立馬寫(xiě)入主存 而是暫時(shí)存在處理器的寄存器中緩存。這樣可以最大化程序的運(yùn)行效率并且允許更好的編譯器優(yōu)化。
以上的錯(cuò)誤是我們假定線(xiàn)程中所有的寫(xiě)操作都可以立馬被其他線(xiàn)程看到。我們需要應(yīng)用一些合適的同步來(lái)確保一個(gè)線(xiàn)程修改被另一個(gè)線(xiàn)程看到的可見(jiàn)性。
同步塊是其中一種實(shí)現(xiàn)這個(gè)可見(jiàn)性的同步方式。同步塊不僅確保原子性也確保可見(jiàn)性。
總結(jié)
以上是生活随笔為你收集整理的java内存块_JVM上的并发和Java内存模型之同步块笔记的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java编程控制电脑硬件_如何快速学习A
- 下一篇: java代码没错却运行不了_Java代码