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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java并发编程实战 代码bug,Java并发编程实战(1)- 并发程序的bug源头

發(fā)布時間:2023/12/10 java 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java并发编程实战 代码bug,Java并发编程实战(1)- 并发程序的bug源头 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

概述

并發(fā)編程一般屬于編程進(jìn)階部分的知識,它會涉及到很多底層知識,包括操作系統(tǒng)。

編寫正確的并發(fā)程序是一件很困難的事情,由并發(fā)導(dǎo)致的bug,有時很難排查或者重現(xiàn),這需要我們理解并發(fā)的本質(zhì),深入分析Bug的源頭。

并發(fā)程序問題的源頭

為了提升系統(tǒng)性能,在過去幾十年中,我們一直在不斷的提升硬件的設(shè)計,包括CPU、內(nèi)存以及I/O設(shè)備,但存在一個主要矛盾:三者之間速度有很大差異,CPU最快,內(nèi)存其次,I/O設(shè)備最慢。

我們編寫的程序,在運行過程中,上述三者都會使用到,在這種情況下,速度慢的內(nèi)存和I/O設(shè)備就會成為瓶頸,為了解決這個問題,計算機體系結(jié)構(gòu)、操作系統(tǒng)和編譯程序做了如下改進(jìn):

CPU增加了緩存,以均衡與內(nèi)存的速度差異。

操作系統(tǒng)增加了進(jìn)程、線程以及分時復(fù)用CPU,從而均衡CPU與I/O設(shè)備的速度差異。

編譯程序優(yōu)化指令執(zhí)行次序,使得緩存能夠得到更加合理的利用。

并發(fā)程序的問題根源也基本來源于上述改進(jìn):

緩存引發(fā)的可見性問題

線程切換引發(fā)的原子性問題

編譯優(yōu)化引發(fā)的有序性問題

接下來我們分別展開描述。

緩存引發(fā)的可見性問題

什么是可見性?

可見性是說一個線程對共享變量的修改,另外一個線程能夠立刻看到。

可見性問題是由CPU緩存引起的,它是在CPU變?yōu)槎嗪撕蟛懦霈F(xiàn)的,單核CPU并不會存在可見性問題。

我們可以參考下面的示意圖。

如圖所示,當(dāng)有2個線程同時訪問內(nèi)存中的變量x時,2個線程運行在不同的CPU上,每個CPU緩存都會保存變量x,線程運行時,會通過CPU緩存來操作x,那么當(dāng)線程1進(jìn)行操作后,線程2并不會立刻得到更新后的x,從而引發(fā)了問題。

我們來看下面的代碼示例,它顯示了對同一個變量使用多個線程進(jìn)行加操作,最后判斷變量值是否符合預(yù)期。

public class ConcurrencyAddDemo {

private long count = 0;

private void add() {

int index = 0;

while (index < 10000) {

count = count + 1;

index++;

}

}

private void reset() {

this.count = 0;

}

private void addTest() throws InterruptedException {

List threads = new ArrayList();

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

threads.add(new Thread(() -> {

this.add();

}));

}

for (Thread thread : threads) {

thread.start();

}

for (Thread thread : threads) {

thread.join();

}

threads.clear();

System.out.println(String.format("Count is %s", count));

}

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

ConcurrencyAddDemo demoObj = new ConcurrencyAddDemo();

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

demoObj.addTest();

demoObj.reset();

}

}

}

程序運行的結(jié)果如下。

Count is 18020

Count is 18857

Count is 16902

Count is 16295

Count is 54453

Count is 59475

Count is 56772

Count is 37376

Count is 60000

Count is 60000

我們可以看到,并不是每次返回的結(jié)果都是60000。

線程切換引發(fā)的原子性問題

什么是原子性?

一個或者多個操作在CPU執(zhí)行的過程中不被中斷的特性,被稱為原子性。原子性可以保證操作執(zhí)行的中間狀態(tài),對外是不可見的。

CPU可以保證的原子操作是在CPU指令級別的,并不是高級語言的操作符,而高級語言中的一個操作,可能會包含多個CPU指令。

以上述代碼中的count = count + 1為例,它至少包含了三條CPU指令:

指令1:首先需要把變量count從內(nèi)存加載到CPU寄存器。

指令2:在寄存器中執(zhí)行+1操作。

指令3:將結(jié)果進(jìn)行保存,這里可能會保存在CPU緩存,也可能保存在內(nèi)存中。

上述指令執(zhí)行過程中,可能會產(chǎn)生”線程切換“,如果多個線程同時執(zhí)行相同的語句,那么因為線程切換,就會導(dǎo)致結(jié)果不是我們期望的。

原子性問題并不只在多核CPU中存在,在單核CPU中也是存在的。

編譯優(yōu)化引發(fā)的有序性問題

什么是有序性?

有序性是指程序按照代碼的先后順序執(zhí)行。

編譯器為了優(yōu)化性能,有時候會改變程序中語句的先后順序,一般情況下,這并不會影響程序的最終結(jié)果,但有時也會引發(fā)意想不到的問題。

我們以典型的單例模式為例進(jìn)行說明,示例代碼如下。

public class SingletonDemo {

private static SingletonDemo instance;

public static SingletonDemo getInstance() {

if (instance == null) {

synchronized(SingletonDemo.class) {

if (instance == null) {

instance = new SingletonDemo();

}

}

}

return instance;

}

}

一般情況下,假設(shè)有兩個線程 A、B 同時調(diào)用 getInstance() 方法,他們會同時發(fā)現(xiàn) instance == null ,于是同時對 Singleton.class 加鎖,此時 JVM 保證只有一個線程能夠加鎖成功(假設(shè)是線程 A),另外一個線程則會處于等待狀態(tài)(假設(shè)是線程 B);線程 A 會創(chuàng)建一個 Singleton 實例,之后釋放鎖,鎖釋放后,線程 B 被喚醒,線程 B 再次嘗試加鎖,此時是可以加鎖成功的,加鎖成功后,線程 B 檢查 instance == null 時會發(fā)現(xiàn),已經(jīng)創(chuàng)建過 Singleton 實例了,所以線程 B 不會再創(chuàng)建一個 Singleton 實例。

但是,如果我們仔細(xì)分析getInstance()方法中的new操作,會發(fā)現(xiàn)它包含以下幾步:

分配一塊內(nèi)存M。

在內(nèi)存M上初始化SingletonDemo對象。

將M的地址賦值給instance變量。

但編譯器可能會做一些優(yōu)化,變成下面的樣子:

分配一塊內(nèi)存M。

將M的地址賦值給instance變量。

在內(nèi)存M上初始化SingletonDemo對象。

這樣很可能導(dǎo)致線程 B獲取instance之后,在instance初始化沒有完全結(jié)束的情況下,調(diào)用它的方法,從而引發(fā)空指針異常。

上述是我們常見的并發(fā)程序的bug源頭,只要我們能夠深刻理解可見性、原子性和有序性在并發(fā)場景下的原理,很多并發(fā)bug就很容易理解了。

參考資料:

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

總結(jié)

以上是生活随笔為你收集整理的Java并发编程实战 代码bug,Java并发编程实战(1)- 并发程序的bug源头的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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