日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java并发问题_并发理论基础:并发问题产生的三大根源

發(fā)布時間:2023/12/2 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java并发问题_并发理论基础:并发问题产生的三大根源 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

并發(fā)問題變幻莫測,一談到并發(fā)就顯得非常高深,一般的程序員對于并發(fā)問題也是頭疼不已,但是隨著網(wǎng)絡互聯(lián)越來越普遍,大規(guī)模用戶訪問網(wǎng)站程序也越來越頻繁,并發(fā)問題又無法避免。

在我們解決并發(fā)問題前首先要理解產(chǎn)生并發(fā)問題的根源是什么,所有并發(fā)處理的工具只是針對這些根源問題的其中一種解決方案,如果只去了解解決方案而不理解問題的根源是什么,那么我們就很難正確的定位問題并對癥下藥。所以要寫好并發(fā)程序我們首先就要深入理解并發(fā)問題產(chǎn)生根源是什么?

起因:如何最大化的利用CPU

CPU運算速度和IO速度的不平衡一直是計算機優(yōu)化的一個課題,我們都知道CPU運算速度要以百倍千倍程度快于IO的速度,而在進行任務的執(zhí)行的時候往往都會需要進行數(shù)據(jù)的IO,正因為這種速度上的差異,所以當CPU和IO一起協(xié)作的時候就產(chǎn)生問題了,CPU執(zhí)行速度非常快,一個任務執(zhí)行時候大部分時間都是在等待IO工作完成,在等待IO的過程中CPU是無法進行其它工作的,所以這樣就使得CPU的資源根本無法合理的運用起來。

CPU就相當于我們計算機的大腦,如何把CPU資源合理的利用起來就直接關系到我們計算機的效率和性能,所以為了這個課題計算機分別從緩存、任務切換、指令排序優(yōu)化這幾個方向進行了優(yōu)化 。

一、進程和線程的產(chǎn)生

在最原始的系統(tǒng)里計算機內(nèi)存中只能允許運行一個程序,這個時候的CPU的能力完全是過剩的,因為CPU在接收到一個任務之后絕大部分時間都是處在IO等待中,CPU根本就利用不起來,所以這個時候就需要一種同時運行多個程序的方法,這樣的話當CPU執(zhí)行一個任務IO等待的時候可以切換到另外一個任務上去執(zhí)行指令,不必在IO上浪費時間,那么CPU就能很大程度的利用起來,所以基于這種思路就產(chǎn)生了進程和線程。

有了進程后,一個內(nèi)存可以劃分出不同的內(nèi)存區(qū)域分別由多個進程管理,當一個進程IO阻塞的時候可以切換到另外一個進程執(zhí)行指令,為了合理公平的把CPU分配到各個進程,CPU把自己的時間分為若干個單位的片段,每在一個進程上執(zhí)行完一個單位的時間就切換到另外一個進程上去執(zhí)行指令,這就是CPU的時間片概念。有了進程后我們的電腦就可以同時運行多個程序了,我們可以一邊看著電影一邊聊天,在CPU的利用率又進一步提升了CPU的利用率。

因為進程做任務切換需要切換內(nèi)存映射地址,而一個進程創(chuàng)建的所有線程,都是共享一個內(nèi)存空間的,所以線程做任務切換成本就很低了,現(xiàn)代的操作系統(tǒng)都基于更輕量的線程來調(diào)度,現(xiàn)在我們提到的“任務切換”都是指“線程切換”。

并發(fā)問題根源之一:CPU切換線程執(zhí)導致的原子性問題

首先我們先理解什么叫原子性,原子性就指是把一個操作或者多個操作視為一個整體,在執(zhí)行的過程不能被中斷的特性叫原子性。

因為IO、內(nèi)存、CPU緩存他們的操作速度有著巨大的差距,假如CPU需要把CPU緩存里的一個變量寫入到磁盤里面,CPU可以馬上發(fā)出一條對應的指令,但是指令發(fā)出后的很長時間CPU都在等待IO的結束,而在這個等待的過程中CPU是空閑的。

所以為了提升CPU的利用率,操作系統(tǒng)就有了進程和時間片的概念,同一個進程里的所有線程都共享一個內(nèi)存空間,CPU每執(zhí)行一個時間段就會切換到另外一個進程處理指令,而這執(zhí)行的時間長度是是以時間片(比如每個時間片為1毫秒)為單位的,通過這種方式讓CPU切換著不同的進程執(zhí)行,讓CPU更好的利用起來,同時也讓我們不同的進程可以同時運行,我們可以一邊操作word文檔,一邊用QQ聊天。

后來操作系統(tǒng)又在CPU切換進程執(zhí)行的基礎上做了進一步的優(yōu)化,以更細的維度“線程”來切換任務執(zhí)行,更加提高了CPU的利用率。但正是這種CPU可以在不同線程中切換執(zhí)行的方式會使得我們程序執(zhí)行的過程中產(chǎn)生原行性問題。

比如說我們以一個變量賦值為例:

語句1:Int number=0;

語句2:number=number+1;

在執(zhí)行語句2的時候,我們的直覺number=number+1 是一個不可分割的整體,但是實際CPU操作過程中并非如此,我們的編譯器會把number=number+1 拆分成多個指令交給CPU執(zhí)行。

number=number+1的指令可能如下:

指令1:CPU把number從內(nèi)存拷貝到CPU緩存。

指令2:把number進行+1的操作。

指令3:把number回寫到內(nèi)存。

在這個時候如果有多線程同時去操作number變量,就很有可能出現(xiàn)問題,因為CPU會在執(zhí)行上面任何一個指令的時候切換線程執(zhí)行指令,這個時候就可能出現(xiàn)執(zhí)行結果與我們預期結果不符合的情況。

比如如果現(xiàn)在有兩個線程都在執(zhí)行number=number+1,結果CPU執(zhí)行流程可能會如下:

執(zhí)行細節(jié):

1、CPU先執(zhí)行線程A的執(zhí)行,把number=0拷貝到CUP寄存器。

2、然后CPU切換到線程B執(zhí)行指令。

3、線程B 把number=0拷貝到CUP寄存器。

4、線程B 執(zhí)行number=number+1 操作得到number=1。

5、線程B把number執(zhí)行結果回寫到緩存里面。

6、然后CPU切換到線程A執(zhí)行指令。

7、線程A執(zhí)行number=number+1 操作得到numbe=1。

8、線程A把number執(zhí)行結果回寫到緩存里面。

9、最后內(nèi)存里面number的值為1。

二、高速緩存的產(chǎn)生

為了減少CPU等待IO的時間,讓CPU有更多的時間是花在運算上,最簡單的思路就是減少IO等待的時間,基于這個思路所以就有了高速緩存增加了高速緩存(L1,L2,L3,主存)。

在計算機系統(tǒng)中,CPU高速緩存是用于減少處理器訪問內(nèi)存所需的時間,其容量遠小于內(nèi)存,但其訪問速度卻是內(nèi)存IO的幾十上百倍。當處理器發(fā)出內(nèi)存訪問請求時,會先查看高速緩存內(nèi)是否有請求數(shù)據(jù)。如果存在(命中),則不需要訪問內(nèi)存直接返回該數(shù)據(jù);如果不存在(失效),則要先把內(nèi)存中的相應數(shù)據(jù)載入緩存,再將其返回處理器。

并發(fā)問題根源之二:緩存導致的可見性問題

在有了高速緩存之后,CPU的執(zhí)行操作數(shù)據(jù)的過程會是這樣的,CPU首先會從內(nèi)存把數(shù)據(jù)拷貝到CPU緩存區(qū)。

然后CPU再對緩存里面的數(shù)據(jù)進行更新等操作,最后CPU把緩存區(qū)里面的數(shù)據(jù)更新到內(nèi)存。

磁盤、內(nèi)存、CPU緩存會按如下形式協(xié)作。

緩存導致的可見性問題就是指我們在操作CPU緩存過程中,由于多個CPU緩存之間獨立不可見的特性,導致共享變量的操作結果無法預期。

在單核CPU時代,因為只有一個核心控制器,所以只會有一個CPU緩存區(qū),這時各個線程訪問的CPU緩存也都是同一個,在這種情況一個線程把共享變量更新到CPU緩存后另外一個線程是可以馬上看見的,因為他們操作的是同一個緩存,所以他們操作后的結果不存在可見性問題。

而隨著CPU的發(fā)展,CPU逐漸發(fā)展成了多核,CPU可以同時使用多個核心控制器執(zhí)行線程任務,當然CPU處理同時處理線程任務的速度也越來越快了,但隨之也產(chǎn)生了一個問題,多核CPU每個核心控制器工作的時候都會有自己獨立的CPU緩存,每個核心控制器都執(zhí)行任務的時候都是操作的自己的CPU緩存,CPU1與CPU2它們之間的緩存是相互不可見的。

這種情況下多個線程操作共享變量就因為緩存不可見而帶來問題,多線程的情況下線程并不一定是在同一個CUP上執(zhí)行,它們?nèi)绻瑫r操作一個共享變量,但因為在不同的CPU執(zhí)行所以他們只能查看和更新自己CPU緩存里的變量值,線程各自的執(zhí)行結果對于別的線程來說是不可見的,所以在并發(fā)的情況下會因為這種緩存不可見的情況會導致問題出現(xiàn)。

比如下面的程序:

兩個線程同時調(diào)用addNumber() 方法對number屬性進行+1 ,循環(huán)10W次,等兩個線程執(zhí)行結束后,我們的預期結果number的值應該是20000,可是我們在多核CPU的環(huán)境下執(zhí)行結果并非我們預期的值。

public class TestCase {

?

private int number=0;

?

public void addNumber(){

for (int i=0;i<100000;i++){

number=number+1;

}

?

}

?

public static void main(String[] args) throws Exception {

TestCase testCase=new TestCase();

Thread threadA=new Thread(new Runnable() {

@Override

public void run() {

testCase.addNumber();

}

});

?

Thread threadB=new Thread(new Runnable() {

@Override

public void run() {

testCase.addNumber();

}

});

threadA.start();

threadB.start();

threadA.join();

threadB.join();

System.out.println("number="+testCase.number);

}

}

打印結果:

三、指令優(yōu)化

進程和線程本質(zhì)上是增加并行的任務數(shù)量來提升CPU的利用率,緩存是通過把IO時間減少來提升CPU的利用率,而指令順序優(yōu)化的初衷的初衷就是想通過調(diào)整CPU指令的執(zhí)行順序和異步化的操作來提升CPU執(zhí)行指令任務的效率。

指令順序優(yōu)化可能發(fā)生在編譯、CPU指令執(zhí)行、緩存優(yōu)化幾個階,其優(yōu)化原則就是只要能保證重排序后不影響單線程的運行結果,那么就允許指令重排序的發(fā)生。其重排序的大體邏輯就是優(yōu)先把CPU比較耗時的指令放到最先執(zhí)行,然后在這些指令執(zhí)行的空余時間來執(zhí)行其他指令,就像我們做菜的時候會把熟的最慢的菜最先開始煮,然后在這個菜熟的時間段去做其它的菜,通過這種方式減少CPU的等待,更好的利用CPU的資源。

并發(fā)問題根源之三:指令優(yōu)化導致的重排序問題

下面的程序代碼如果init()方法的代碼經(jīng)過了指令重排序后,兩個方法在兩個不同的線程里面調(diào)用就可能出現(xiàn)問題。

private static int value;

private static boolean flag;

?

public static void init(){

value=8; //語句1 flag=true; //語句2 }

?

public static void getValue(){

if(flag){

System.out.println(value);

}

}

根據(jù)上面代碼,如果程序代碼運行都是按順序的,那么getValue() 中打印的value值必定是等于8的,不過如果init()方法經(jīng)過了指令重排序,那么結果就不一定了。根據(jù)重排序原則,init()方法進行指令重排序重排序后并不會影響其運行結果,因為語句1和語句2之間沒有依賴關系。 所以進行重排序后代碼執(zhí)行順序可能如下。

flag=true; //語句2 value=8; //語句1

如果init()方法經(jīng)過了指令重排序后,這個時候兩個線程分別調(diào)用 init()和getValue()方法,那么就有可能出現(xiàn)下圖的情況,導致最終打印出來的value數(shù)據(jù)等于0。

總結

以上是生活随笔為你收集整理的java并发问题_并发理论基础:并发问题产生的三大根源的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。