摘要:在之前的文章中學(xué)習(xí)了關(guān)鍵字,可以保證變量在線(xiàn)程間的可見(jiàn)性,但他不能真正的保證線(xiàn)程安全。線(xiàn)程執(zhí)行到指令時(shí),將會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的的所有權(quán),即嘗試獲得對(duì)象的鎖。從可見(jiàn)性上來(lái)說(shuō),線(xiàn)程通過(guò)持有鎖的方式獲取變量的最新值。
在之前的文章中學(xué)習(xí)了volatile關(guān)鍵字,volatile可以保證變量在線(xiàn)程間的可見(jiàn)性,但他不能真正的保證線(xiàn)程安全。
/** * @author cenkailun * @Date 9/5/17 * @Time 20:23 */ public class ConcurrentAddWithVolatile implements Runnable { private static ConcurrentAddWithVolatile instance = new ConcurrentAddWithVolatile(); private static volatile int i = 0; public static void increase() { i++; } public void run() { for (int j = 0; j < 1000000; j++) { i++; } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance,"線(xiàn)程1"); Thread t2 = new Thread(instance, "線(xiàn)程2"); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
如上述代碼所示,如果說(shuō)兩個(gè)線(xiàn)程是正確的并發(fā)執(zhí)行的話(huà),最后得到的結(jié)果應(yīng)該是2000000,但結(jié)果往往是小于2000000。那么這是為什么呢?
經(jīng)過(guò)閱讀書(shū)籍,可以得知,i++的這個(gè)操作,其實(shí)是要分成3步。
1. 讀取i的當(dāng)前值到操作棧 2. 對(duì)i的當(dāng)前值+1 3. 寫(xiě)回i+1后的值
經(jīng)過(guò)了上述3步,才完成了i++ 的這個(gè)操作,volatile保證了寫(xiě)回內(nèi)存后,i的最新值能夠被其他線(xiàn)程獲取,但i++的這三個(gè)動(dòng)作不是一個(gè)整體,即不是原子操作,是可以被拆開(kāi)的。
比如,線(xiàn)程1和2同時(shí)讀取了i為0,并各自在自己的線(xiàn)程中計(jì)算得到i=1,先后寫(xiě)入這個(gè)i的值,導(dǎo)致雖然i++被執(zhí)行了兩次,但是實(shí)際i的值只增加了1。
如果要解決這個(gè)問(wèn)題,就要保證多個(gè)線(xiàn)程在對(duì)i進(jìn)行++ 這個(gè)操作時(shí)完全同步,即i++的這三步是一起完成的,當(dāng)線(xiàn)程1在寫(xiě)入時(shí),其他線(xiàn)程不能讀也不能寫(xiě),因?yàn)樵诰€(xiàn)程1寫(xiě)完之前,其他線(xiàn)程讀到的肯定是一個(gè)過(guò)期的數(shù)據(jù)。
Java提供了synchronized來(lái)實(shí)現(xiàn)這個(gè)功能,保證多線(xiàn)程執(zhí)行時(shí)候的同步,某一時(shí)刻只有一個(gè)線(xiàn)程可以對(duì)synchronized關(guān)鍵字保護(hù)起來(lái)的區(qū)域進(jìn)行操作,相對(duì)于volatile來(lái)說(shuō)是比較重量級(jí)的。
Java的synchronized關(guān)鍵字具體表現(xiàn)有以下三種形式:
作用于實(shí)例方法,鎖的是當(dāng)前實(shí)例對(duì)象。
作用于靜態(tài)方法,鎖的是當(dāng)前類(lèi)。
作用于代碼塊,鎖的是Synchronized里配置的對(duì)象。
下面是一個(gè)示例,將synchronized作用于一個(gè)給定對(duì)象instance,每當(dāng)線(xiàn)程要進(jìn)入被包裹的代碼塊,會(huì)請(qǐng)求instance的鎖。如果有其他線(xiàn)程已經(jīng)持有了這把鎖,那么新到的線(xiàn)程就必須等待,這樣保證了每次只有一個(gè)線(xiàn)程會(huì)執(zhí)行i++操作。
/** * @author cenkailun * @Date 9/5/17 * @Time 20:23 */ public class ConcurrentAddWithVolatile implements Runnable { private static ConcurrentAddWithVolatile instance = new ConcurrentAddWithVolatile(); private static volatile int i = 0; public static void increase() { i++; } public void run() { for (int j = 0; j < 1000000; j++) { synchronized (instance) { //同步代碼塊 i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance,"線(xiàn)程1"); Thread t2 = new Thread(instance, "線(xiàn)程2"); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
對(duì)于java中的代碼塊同步,JVM是基于進(jìn)入和退出Monitor對(duì)象來(lái)實(shí)現(xiàn)代碼塊同步的,將monitorenter指令插入到同步代碼塊的開(kāi)始位置,monitorexit插入到方法結(jié)束處和異常處,每一個(gè)對(duì)象都有一個(gè)monitor與之對(duì)應(yīng),當(dāng)一個(gè)monitor被持有后,它將處于鎖定狀態(tài)。線(xiàn)程執(zhí)行到monitorenter指令時(shí),將會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的monitor的所有權(quán),即嘗試獲得對(duì)象的鎖。
如下面字節(jié)碼所示,代表上文代碼中的同步代碼塊。
13: monitorenter 14: getstatic #2 // Field i:I 17: iconst_1 18: iadd 19: putstatic #2 // Field i:I 22: aload_2 23: monitorexit
對(duì)于實(shí)例方法或者靜態(tài)方法上加的synchronized關(guān)鍵字,在方法上會(huì)有一個(gè)標(biāo)志位代表,如下面字節(jié)碼所示。
public synchronized void increase(); flags: ACC_PUBLIC, ACC_SYNCHRONIZED
在我看來(lái),synchronized相對(duì)于volatile的強(qiáng)大之處在于保證了線(xiàn)程安全性以及做到了線(xiàn)程同步,同時(shí)也能做到volatile提供的線(xiàn)程間可見(jiàn)性以及有序性。從可見(jiàn)性上來(lái)說(shuō),線(xiàn)程通過(guò)持有鎖的方式獲取變量的最新值。從有序性上來(lái)說(shuō),synchronized限制每次只有一個(gè)線(xiàn)程可以訪(fǎng)問(wèn)同步的代碼,無(wú)論內(nèi)部指令順序如何被打亂,jvm會(huì)保證最終執(zhí)行的結(jié)果總是一樣,其他線(xiàn)程只能在獲得鎖后讀取結(jié)果數(shù)據(jù),不會(huì)讀到中間值,所以有序性問(wèn)題也得到了解決。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70341.html
摘要:目前看的部分主要是這個(gè)關(guān)鍵字。語(yǔ)言提供了,保證了所有線(xiàn)程能看到共享變量最新的值。前綴的指令在多核處理器下會(huì)做兩件事情將當(dāng)前處理器緩存行的數(shù)據(jù)寫(xiě)回到系統(tǒng)內(nèi)存。 這一章節(jié)的話(huà),主要是講一下在并發(fā)操作中常見(jiàn)的volatile、synchronized以及原子操作的相關(guān)知識(shí)。 目前看的部分主要是volatile這個(gè)關(guān)鍵字。 volatile 根據(jù)Java語(yǔ)言規(guī)范第3版中對(duì)volatile的定義...
摘要:前言今天的筆記來(lái)了解一下原子操作以及中如何實(shí)現(xiàn)原子操作。概念原子本意是不能被進(jìn)一步分割的最小粒子,而原子操作意為不可被中斷的一個(gè)或一系列操作。處理器實(shí)現(xiàn)原子操作處理器會(huì)保證基本內(nèi)存操作的原子性。 showImg(https://segmentfault.com/img/bVVIRA?w=1242&h=536); 前言 今天的筆記來(lái)了解一下原子操作以及Java中如何實(shí)現(xiàn)原子操作。 概念 ...
摘要:前言并發(fā)編程的目的是讓程序跑的更快,但并不是啟動(dòng)更多的線(xiàn)程,這個(gè)程序就跑的更快。盡可能降低上下文切換的次數(shù),有助于提高并發(fā)效率。死鎖并發(fā)編程中的另一挑戰(zhàn)是死鎖,會(huì)造成系統(tǒng)功能不可用。 前言 并發(fā)編程的目的是讓程序跑的更快,但并不是啟動(dòng)更多的線(xiàn)程,這個(gè)程序就跑的更快。有以下幾種挑戰(zhàn)。 挑戰(zhàn)及方案 上下文切換 單核CPU上執(zhí)行多線(xiàn)程任務(wù),通過(guò)給每個(gè)線(xiàn)程分配CPU時(shí)間片的方式來(lái)實(shí)現(xiàn)這個(gè)機(jī)制。...
摘要:開(kāi)頭正式開(kāi)啟我入職的里程,現(xiàn)在已是工作了一個(gè)星期了,這個(gè)星期算是我入職的過(guò)渡期,算是知道了學(xué)校生活和工作的差距了,總之,盡快習(xí)慣這種生活吧。當(dāng)時(shí)是看的廖雪峰的博客自己也用做爬蟲(chóng)寫(xiě)過(guò)幾篇博客,不過(guò)有些是在前人的基礎(chǔ)上寫(xiě)的。 showImg(https://segmentfault.com/img/remote/1460000010867984); 開(kāi)頭 2017.08.21 正式開(kāi)啟我...
摘要:假設(shè)不發(fā)生編譯器重排和指令重排,線(xiàn)程修改了的值,但是修改以后,的值可能還沒(méi)有寫(xiě)回到主存中,那么線(xiàn)程得到就是很自然的事了。同理,線(xiàn)程對(duì)于的賦值操作也可能沒(méi)有及時(shí)刷新到主存中。線(xiàn)程的最后操作與線(xiàn)程發(fā)現(xiàn)線(xiàn)程已經(jīng)結(jié)束同步。 很久沒(méi)更新文章了,對(duì)隔三差五過(guò)來(lái)刷更新的讀者說(shuō)聲抱歉。 關(guān)于 Java 并發(fā)也算是寫(xiě)了好幾篇文章了,本文將介紹一些比較基礎(chǔ)的內(nèi)容,注意,閱讀本文需要一定的并發(fā)基礎(chǔ)。 本文的...
閱讀 885·2021-10-27 14:19
閱讀 1102·2021-10-15 09:42
閱讀 1526·2021-09-14 18:02
閱讀 738·2019-08-30 13:09
閱讀 2981·2019-08-29 15:08
閱讀 2082·2019-08-28 18:05
閱讀 948·2019-08-26 10:25
閱讀 2777·2019-08-23 16:28