摘要:在多線程的問(wèn)題上面概念比較多,也需要慢慢理解,其實(shí)也在多線程的鎖的上面做了很多優(yōu)化,還有互斥同步和非互斥同步,還有很多概念,什么是自旋和自適應(yīng)自旋,鎖消除順便提一下,上面的字符串拼接的例子就是用到了這種優(yōu)化方式,鎖粗化,我們下次再繼續(xù)分享。
在我們平常的開(kāi)發(fā)工作中,或多或少的都能接觸到多線程編程或者一些并發(fā)問(wèn)題,隨著操作系統(tǒng)和系統(tǒng)硬件的升級(jí),并發(fā)編程被越來(lái)越多的運(yùn)用到我們的開(kāi)發(fā)中,我們使用多線程的最初的想法是能夠更大程度的利用系統(tǒng)資源,但是我們?cè)谑褂枚嗑€程的時(shí)候,也會(huì)有一些問(wèn)題的存在,我們先來(lái)看一段代碼。
private static int i = 0; private static void increse(){ i++; } public static void main(String[] args) { Thread[] threads = new Thread[20]; for (int i = 0; i < threads.length; i++){ threads[i] = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++){ increse(); } } }); threads[i].start(); } while (Thread.activeCount() > 1){ Thread.yield(); } System.out.println(i); }
首先看看這段代碼是沒(méi)有問(wèn)題的,但是如果在多線程的環(huán)境中,這段代碼運(yùn)行的結(jié)果基本是都不一樣的,這里是開(kāi)啟20個(gè)線程,然后每一個(gè)線程調(diào)用increse()方法對(duì)變量i進(jìn)行一個(gè)賦值操作,預(yù)期的一個(gè)輸出應(yīng)該是200000,但是為什么會(huì)每一次的輸出都不太一樣呢?原因就在于這個(gè)地方i++,這里就是產(chǎn)生并發(fā)問(wèn)題的根本原因,那看起來(lái)很簡(jiǎn)單的一個(gè)i++為什么會(huì)有這種問(wèn)題?這里就簡(jiǎn)單的從java內(nèi)存模型(JMM)來(lái)了解一下,首先JMM規(guī)定,每一個(gè)線程運(yùn)行時(shí)都會(huì)有一個(gè)工作內(nèi)存,然后變量i是存儲(chǔ)在主內(nèi)存的,每次線程在計(jì)算數(shù)據(jù)的時(shí)候都要去主內(nèi)存中獲取當(dāng)前變量的值,那么簡(jiǎn)單的來(lái)說(shuō),就是一個(gè)線程將變量i計(jì)算得到結(jié)果后,還沒(méi)有將這個(gè)數(shù)據(jù)刷新到主內(nèi)存,在這個(gè)時(shí)候,其他的線程已經(jīng)獲取到了原來(lái)的值,換句話說(shuō),本線程中獲取到的數(shù)據(jù)是否是最新的,這個(gè)是不知道的。但是這只是從JMM角度來(lái)簡(jiǎn)單的說(shuō)一下,i++這個(gè)看似簡(jiǎn)單的操作其實(shí)包含了三個(gè)操作,獲取i的值,對(duì)i進(jìn)行自增,然后對(duì)i進(jìn)行賦值。就是在這幾個(gè)操作中,其他的線程會(huì)有很多的時(shí)間來(lái)做很多的事情,那有人會(huì)問(wèn),是不是將這個(gè)i++操作同步就可以了?是的,那該如何同步呢?
有人說(shuō)用volatile來(lái)修飾一下變量i不就可以了么?是的,java中volatile這個(gè)關(guān)鍵字確實(shí)是提供了一個(gè)同步的功能,但是為什么在這里修改一下還是沒(méi)有效果呢?原因就在于如果一個(gè)變量用volatile修飾之后,只是會(huì)讓其他的線程立即能夠知道當(dāng)前變量的值是多少,這里就叫做可見(jiàn)性,但是還是解決不了i++這幾個(gè)操作的問(wèn)題,那如何處理,又有人提出用synchronized這個(gè)關(guān)鍵字,不得不承認(rèn),這個(gè)關(guān)鍵字確實(shí)很強(qiáng)大,是能夠解決這個(gè)問(wèn)題,那我們有沒(méi)有考慮過(guò)這個(gè)為什么可以解決這個(gè)問(wèn)題,是如何解決的,那還是簡(jiǎn)單的說(shuō)一下,首先synchronized是屬于JVM級(jí)別的,有這個(gè)關(guān)鍵字的方法或者代碼塊,最后會(huì)被解釋成monitorenter和monitorexit指令,這兩個(gè)字節(jié)碼都明確需要一個(gè)reference類(lèi)型的參數(shù)來(lái)指出要鎖定或者解鎖的對(duì)象,像這樣:
public synchronized String f(){ //code } synchronized(object){ //code }
看到這里,我們應(yīng)該能明白synchronized為什么可以解決上面程序的問(wèn)題,但是我們還應(yīng)該要明確一個(gè)概念就是原子性,換句話說(shuō),就是我們?cè)谔幚硪恍┒嗑€程的問(wèn)題的時(shí)候,應(yīng)該保證一些共享數(shù)據(jù)的操作是原子性的,這樣才能保證正確性,看到這里,相信你也有了一個(gè)大概的理解,那我們來(lái)總結(jié)一下,在處理多線程的問(wèn)題的時(shí)候,哪些點(diǎn)是值得注意的,可見(jiàn)性,原子性,有序性,這幾個(gè)點(diǎn)是保證多線程能夠正確的一個(gè)前提條件,至于什么是有序性,這里涉及到內(nèi)存指令的重排序,不在討論范圍內(nèi),以后再來(lái)討論。
這里還要指出一個(gè)問(wèn)題,就是是否我們?cè)谔幚矶嗑€程問(wèn)題的時(shí)候,一定要同步,或者說(shuō)一定要加鎖,這個(gè)也不是一定的,之前網(wǎng)上有一個(gè)說(shuō)笑的方式,就是我們?cè)谔幚矶嗑€程的問(wèn)題的時(shí)候,有時(shí)候就會(huì)發(fā)現(xiàn),代碼又被寫(xiě)成了單線程,當(dāng)然這只是一個(gè)玩笑話,但是這里我們也能看出來(lái),是不是單線程的程序就不會(huì)有這些問(wèn)題?答案是肯定的,因?yàn)閱尉€程不存在資源競(jìng)爭(zhēng)的問(wèn)題,也就不需要再討論了。
那么我們什么時(shí)候需要使用同步,什么時(shí)候又不需要呢?我們來(lái)看一段代碼
public String f(String s1, String s2, String s3){ return s1 + s2 +s3; }
這是一個(gè)字符串拼接的一個(gè)方法,我們來(lái)反編譯看一下,這里JVM到底是怎么做的?
這里很明顯的能夠看出來(lái),最后是通過(guò)StringBuilder來(lái)為我們生成了最后的結(jié)果,那有人會(huì)問(wèn),這里線程安全么?是的,這里是線程安全的,因?yàn)樵谶@個(gè)方法中,雖然也有變量的使用,但是都是屬于線程內(nèi)部在使用,其他的線程根本不會(huì)訪問(wèn)到或者說(shuō)這些變量也不會(huì)讓其他線程訪問(wèn)到,我們稱(chēng)其為沒(méi)有方法逃逸,也就是說(shuō)只能在本線程中使用這些變量,這里是線程安全的,至于什么是逃逸分析,簡(jiǎn)單的提一下就是這是JVM的高級(jí)優(yōu)化的一種方式,說(shuō)的再簡(jiǎn)單一點(diǎn),就是別的線程訪問(wèn)不到這個(gè)變量,這樣的代碼是不需要同步的。
在多線程的問(wèn)題上面概念比較多,也需要慢慢理解,其實(shí)JVM也在多線程的鎖的上面做了很多優(yōu)化,還有互斥同步和非互斥同步,還有很多概念,什么是自旋和自適應(yīng)自旋,鎖消除(順便提一下,上面的字符串拼接的例子就是用到了這種優(yōu)化方式),鎖粗化,我們下次再繼續(xù)分享。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76658.html
摘要:物理計(jì)算機(jī)并發(fā)問(wèn)題在介紹內(nèi)存模型之前,先簡(jiǎn)單了解下物理計(jì)算機(jī)中的并發(fā)問(wèn)題?;诟咚倬彺娴拇鎯?chǔ)交互引入一個(gè)新的問(wèn)題緩存一致性。寫(xiě)入作用于主內(nèi)存變量,把操作從工作內(nèi)存中得到的變量值放入主內(nèi)存的變量中。 物理計(jì)算機(jī)并發(fā)問(wèn)題 在介紹Java內(nèi)存模型之前,先簡(jiǎn)單了解下物理計(jì)算機(jī)中的并發(fā)問(wèn)題。由于處理器的與存儲(chǔ)設(shè)置的運(yùn)算速度有幾個(gè)數(shù)量級(jí)的差距,所以現(xiàn)代計(jì)算機(jī)加入一層讀寫(xiě)速度盡可能接近處理器的高速緩...
摘要:第一個(gè)字被稱(chēng)為。經(jīng)量級(jí)鎖的加鎖過(guò)程當(dāng)一個(gè)對(duì)象被鎖定時(shí),被復(fù)制到當(dāng)前嘗試獲取鎖的線程的線程棧的鎖記錄空間被復(fù)制的官方稱(chēng)為。根據(jù)鎖對(duì)象目前是否處于被鎖定狀態(tài),撤銷(xiāo)偏向后恢復(fù)到未鎖定或經(jīng)量級(jí)鎖定狀態(tài)。 Synchronized關(guān)鍵字 synchronized的鎖機(jī)制的主要優(yōu)勢(shì)是Java語(yǔ)言?xún)?nèi)置的鎖機(jī)制,因此,JVM可以自由的優(yōu)化而不影響已存在的代碼。 任何對(duì)象都擁有對(duì)象頭這一數(shù)據(jù)結(jié)構(gòu)來(lái)支持鎖...
摘要:內(nèi)存模型是圍繞著在并發(fā)過(guò)程中如何處理原子性可見(jiàn)性和有序性這個(gè)特征來(lái)建立的,我們來(lái)看下哪些操作實(shí)現(xiàn)了這個(gè)特性。可見(jiàn)性可見(jiàn)性是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。 Java內(nèi)存模型是圍繞著在并發(fā)過(guò)程中如何處理原子性、可見(jiàn)性和有序性這3個(gè)特征來(lái)建立的,我們來(lái)看下哪些操作實(shí)現(xiàn)了這3個(gè)特性。 原子性(atomicity): 由Java內(nèi)存模型來(lái)直接保證原子性變量操作包括...
摘要:這兩種策略的區(qū)別就在于,公平策略會(huì)讓等待時(shí)間長(zhǎng)的線程優(yōu)先執(zhí)行,非公平策略則是等待時(shí)間長(zhǎng)的線程不一定會(huì)執(zhí)行,存在一個(gè)搶占資源的問(wèn)題。 之前有一篇文章我們簡(jiǎn)單的談到了Java中同步的問(wèn)題,但是可能在平常的開(kāi)發(fā)中,有些理論甚至是某些方式是用不到的,但是從程序的角度看,這些理論思想我們可以運(yùn)用到我們的開(kāi)發(fā)中,比如是不是應(yīng)該一談到同步問(wèn)題,就應(yīng)該想到用synchronized?,什么時(shí)候應(yīng)該用R...
摘要:并發(fā)需要解決的問(wèn)題功能性問(wèn)題線程同步面臨兩個(gè)問(wèn)題,想象下有兩個(gè)線程在協(xié)作工作完成某項(xiàng)任務(wù)。鎖可用于規(guī)定一個(gè)臨界區(qū),同一時(shí)間臨界區(qū)內(nèi)僅能由一個(gè)線程訪問(wèn)。并發(fā)的數(shù)據(jù)結(jié)構(gòu)線程安全的容器,如等。 并發(fā)指在宏觀上的同一時(shí)間內(nèi)同時(shí)執(zhí)行多個(gè)任務(wù)。為了滿(mǎn)足這一需求,現(xiàn)代的操作系統(tǒng)都抽象出 線程 的概念,供上層應(yīng)用使用。 這篇博文不打算詳細(xì)展開(kāi)分析,而是對(duì)java并發(fā)中的概念和工具做一個(gè)梳理。沿著并發(fā)模...
閱讀 2952·2021-10-14 09:50
閱讀 1267·2021-10-08 10:21
閱讀 3701·2021-10-08 10:16
閱讀 3108·2021-09-27 14:02
閱讀 3170·2021-09-23 11:21
閱讀 2215·2021-09-07 10:17
閱讀 435·2019-08-30 14:00
閱讀 2156·2019-08-29 17:26