第2章 對象及變量的并發(fā)訪問
概念
“非線程安全”其實(shí)會在多個線程對同一個對象中的實(shí)例變量進(jìn)行并發(fā)訪問時發(fā)生,產(chǎn)生的后果就是“臟讀”,也就是取到的數(shù)據(jù)其實(shí)是被更改過的
“非線程安全”問題存在于“實(shí)例變量”中(和數(shù)據(jù)庫),如果是方法內(nèi)部的私有變量,則不存在“非線程安全”問題,所得結(jié)果也就是“線程安全”的了。
如果多個線程共同訪問1個對象中的實(shí)例變量,則有可能出現(xiàn)“非線程安全”問題。
只有共享資源(庫存)的讀寫訪問才需要同步化。
例如
1)A線程先持有object對象的Lock鎖,B線程可以以異步的方式調(diào)用object對象中的非synchronized類型的方法。
2)A線程先持有object對象的Lock鎖,B線程如果在這時調(diào)用object對象中的synchronized類型的方法則需等待,也就是同步。
可重入鎖:自己可以再次獲得自己的內(nèi)部鎖。比如有1條線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當(dāng)其于次想要獲取這個對象的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。
可重入鎖也支持在父子類繼承的環(huán)境中。
當(dāng)存在父子類繼承關(guān)系時,子類是完全可以通過“可重入鎖”調(diào)用父類的同步方法的。
當(dāng)一個線程執(zhí)行的代碼出現(xiàn)異常時,其所持有的鎖會自動釋放。
synchronized關(guān)鍵字
關(guān)鍵字
synchronized
取得的鎖都是對象鎖,而不是把一段代碼或方法(函數(shù))當(dāng)作鎖,所以在上面的示例中,哪個線程先執(zhí)行帶synchronized
關(guān)鍵字的方法,哪個線程就持有該方法所屬對象的鎖Lock,那么其他線程只能呈等待狀態(tài),前提是多個線程訪問的是同一個對象。同步是可以被繼承的,即父類中帶有
synchronized
關(guān)鍵字的方法在子類中也具有synchronized
關(guān)鍵字該有的特性。當(dāng)子類重寫父類這個synchronized
方法時可以不加synchronized
關(guān)鍵字,這時子類重寫的這個方法也沒有同步特性了,反之亦然。
關(guān)于上一條有必要說明一下:原文中是這樣寫的“同步不能繼承,所以還得在子類的方法中添加
synchronized
關(guān)鍵字”,個人對繼承的理解是子類繼承父類后,子類繼承了父類中可以被繼承的屬性和方法,這時子類可以使用繼承過來的這些屬性與方法,并非此書作中所講到的例子用重寫來說明子類不能繼承父類帶有synchronized
關(guān)鍵字的方法的同步特性。
不在
synchronized
塊中就是異步執(zhí)行,在synchronized
塊中就是同步執(zhí)行。在使用同步
synchronized(this)
代碼塊時需要注意的是,當(dāng)一個線程訪問object的一個synchronized(this)
同步代碼塊時,其他線程對同一個object中所有其他synchronized(this)
同步代碼的訪問將被阻塞,這說明synchronized
使用的“對象監(jiān)視器”是一個。synchronized(this)
代碼塊是鎖定當(dāng)前對象的synchronized
關(guān)鍵字加到static
靜態(tài)方法上是給Class
類上鎖,而synchronized
關(guān)鍵字加到非static
方法上是給對象上鎖。多線程的死鎖:多個線程多個鎖,代碼問題上會出現(xiàn)死鎖??梢允褂肑DK自帶的工具來監(jiān)測是否有死鎖現(xiàn)象。
1)在jdk安裝目錄下執(zhí)行命令jps
2)得到運(yùn)行的線程Run的id值1234,再執(zhí)行命令jstack,查看結(jié)果
3)若有死鎖會有提示類似'Found 1 deadlock.'
作為鎖的對象的引用不變,即使其中的屬性改變后也和之前是同一個鎖
關(guān)鍵字
synchronized
可以使多個線程訪問同一個資源具有同步性,而且它還具有將線程工作內(nèi)存中的私有變量與公共內(nèi)存中的變量同步的功能。關(guān)鍵字
synchronized
可以保證在同一時刻,只有一個線程可以執(zhí)行某一個方法或某一個代碼塊。它包含兩個特征:互斥性和可見性。同步synchronized
不僅可以解決一個線程看到對象處于不一致的狀態(tài),還可以保證進(jìn)入同步方法或者同步代碼塊的每個線程,都看到由同一個鎖保護(hù)之前所有的修改效果。(辯證著看)
volatile關(guān)鍵字
- volatile的主要作用是使變量在多個線程間可見,但其和原子性并沒有什么關(guān)系。(但在這個服務(wù)器集群的年代單實(shí)例上的共享變量可見性可能并沒什么大用)
volatile private boolean isRunning = false;
volatile修飾的屬性當(dāng)被修改時會強(qiáng)制將修改的值立即寫入主存,此時會使其它線程工作內(nèi)存中的此變量緩存失效,當(dāng)這些線程要讀取時發(fā)現(xiàn)緩存失效就會去主存中讀最新的值。
synchronized
與volatile
的比較
1)volatile是線程同步的輕量實(shí)現(xiàn),所以性能肯定比synchronized要好。volatile只能修飾變量,synchronized可以修飾方法和代碼塊。
2)多線程訪問volatile不會發(fā)生阻塞,而synchronized會。
3)volatile能保證數(shù)據(jù)的可見性,但不能保證原子性;而synchronized可以保證原子性也可以間接保證可見性,因?yàn)樗鼤⑺接袃?nèi)存和公共內(nèi)存中的數(shù)據(jù)做同步。(個人認(rèn)為并非如此而是happen-before的關(guān)系)
4)volatile解決的是變量在多線程之間的可見性;而synchronized解決的是多個線程之間訪問資源的同步性。
volatile關(guān)鍵字解決的是變量讀時的可見性問題,但無法保證原子性,對于多線程訪問同一個實(shí)例變量還是需要加鎖同步。
線程安全包括原子性和可見性兩個方面,Java的同步機(jī)制都是圍繞這兩個方面來確保線程安全的。
原子操作是不能分割的整體,沒有其他線程能夠中斷或檢查正在原子操作中的變量