日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
面試突擊:線程安全問題是如何產(chǎn)生的?

舉個例子來說,比如銀行只有張三一個人來辦理業(yè)務,這種情況在程序中就叫做單線程執(zhí)行,而單線程執(zhí)行是沒有問題的,也就是線程安全的。但突然有一天來了很多人同時辦理業(yè)務,這種情況就叫做多線程執(zhí)行。如果所有人都一起爭搶著辦理業(yè)務,很有可能會導致錯誤,而這種錯誤就叫非線程安全。如果每個人都能有序排隊辦理業(yè)務,且工作人員不會操作失誤,我們就把這種情況稱之為線程安全的。

問題演示

接下來我們演示一下,程序中非線程安全的示例。我們先創(chuàng)建一個變量 number 等于 0,然后開啟線程 1 執(zhí)行 100 萬次 number++ 操作,同時再開啟線程 2 執(zhí)行 100 萬次 number-- 操作,等待線程 1 和線程 2 都執(zhí)行完,正確的結(jié)果 number 應該還是 0,但不加干預的多線程執(zhí)行結(jié)果卻與預期的正確結(jié)果不一致,如下代碼所示:

public class ThreadSafeTest {
// 全局變量
private static int number = 0;
// 循環(huán)次數(shù)(100W)
private static final int COUNT = 1_000_000;

public static void main(String[] args) throws InterruptedException {
// 線程1:執(zhí)行 100W 次 number+1 操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number++;
}
});
t1.start();

// 線程2:執(zhí)行 100W 次 number-1 操作
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number--;
}
});
t2.start();

// 等待線程 1 和線程 2,執(zhí)行完,打印 number 最終的結(jié)果
t1.join();
t2.join();
System.out.println("number 最終結(jié)果:" + number);
}
}

以上程序的執(zhí)行結(jié)果如下圖所示:

從上述執(zhí)行結(jié)果可以看出,number 變量最終的結(jié)果并不是 0,和我們預期的正確結(jié)果是不相符的,這就是多線程中的線程安全問題。

產(chǎn)生原因

導致線程安全問題的因素有以下 5 個:

  • 多線程搶占式執(zhí)行。
  • 多線程同時修改同一個變量。
  • 非原子性操作。
  • 內(nèi)存可見性。
  • 指令重排序。

接下來我們分別來看這 5 個因素的具體含義。

1.多線程搶占式執(zhí)行

導致線程安全問題的第一大因素就是多線程搶占式執(zhí)行,想象一下,如果是單線程執(zhí)行,或者是多線程有序執(zhí)行,那就不會出現(xiàn)混亂的情況了,不出現(xiàn)混亂的情況,自然就不會出現(xiàn)非線程安全的問題了。

2.多線程同時修改同一個變量

如果是多線程同時修改不同的變量(每個線程只修改自己的變量),也是不會出現(xiàn)非線程安全的問題了,比如以下代碼,線程 1 修改 number1 變量,而線程 2 修改 number2 變量,最終兩個線程執(zhí)行完之后的結(jié)果如下:

public class ThreadSafe {
// 全局變量
private static int number = 0;
// 循環(huán)次數(shù)(100W)
private static final int COUNT = 1_000_000;
// 線程 1 操作的變量 number1
private static int number1 = 0;
// 線程 2 操作的變量 number2
private static int number2 = 0;

public static void main(String[] args) throws InterruptedException {
// 線程1:執(zhí)行 100W 次 number+1 操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number1++;
}
});
t1.start();

// 線程2:執(zhí)行 100W 次 number-1 操作
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number2--;
}
});
t2.start();

// 等待線程 1 和線程 2,執(zhí)行完,打印 number 最終的結(jié)果
t1.join();
t2.join();
number = number1 + number2;
System.out.println("number=number1+number2 最終結(jié)果:" + number);
}
}

以上程序的執(zhí)行結(jié)果如下圖所示:

從上述結(jié)果可以看出,多線程只要不是同時修改同一個變量,也不會出現(xiàn)線程安全問題。

3.非原子性操作

原子性操作是指操作不能再被分隔就叫原子性操作。比如人類吸氣或者是呼氣這個動作,它是一瞬間一次性完成的,你不可能先吸一半(氣),停下來玩會手機,再吸一半(氣),這種操作就是原子性操作。而非原子性操作是我現(xiàn)在要去睡覺,但睡覺之前要先上床,再拉被子,再躺下、再入睡等一系列的操作綜合在一起組成的,這就是非原子性操作。非原子性操作是有可以被分隔和打斷的,比如要上床之前,發(fā)現(xiàn)時間還在,先刷個劇、刷會手機、再玩會游戲,甚至是再吃點小燒烤等等,所以非原子性操作有很多不確定性,而這些不確定性就會造成線程安全問題問題。像 i++ 和 i-- 這種操作就是非原子的,它在 +1 或 -1 之前,先要查詢原變量的值,并不是一次性完成的,所以就會導致線程安全問題。比如以下操作流程:

操作步驟

線程1

線程2

T1

讀取到 number=1,準備執(zhí)行 number-1 的操作,但還沒有執(zhí)行,時間片就用完了。

T2

讀取到 number=1,并且執(zhí)行 number+1 操作,將 number 修改成了 2。

T3

恢復執(zhí)行,因為之前已經(jīng)讀取了 number=1,所以直接執(zhí)行 -1 操作,將 number 變成了 0。

以上就是一個經(jīng)典的錯誤,number 原本等于 1,線程 1 進行 -1 操作,而線程 2 進行加 1,最終的結(jié)果 number 應該還等于 1 才對,但通過上面的執(zhí)行,number 最終被修改成了 0,這就是非原子性導致的問題。

4.內(nèi)存可見性問題

在 Java 編程中內(nèi)存分為兩種類型:工作內(nèi)存和主內(nèi)存,而工作內(nèi)存使用的是 CPU 寄存器實現(xiàn)的,而主內(nèi)存是指電腦中的內(nèi)存,我們知道 CPU 寄存器的操作速度是遠大于內(nèi)存的操作速度的,它們的性能差異如下圖所示:

那這和線程安全有什么關系呢?這是因為在 Java 語言中,為了提高程序的執(zhí)行速度,所以在操作變量時,會將變量從主內(nèi)存中復制一份到工作內(nèi)存,而主內(nèi)存是所有線程共用的,工作內(nèi)存是每個線程私有的,這就會導致一個線程已經(jīng)把主內(nèi)存中的公共變量修改了,而另一個線程不知道,依舊使用自己工作內(nèi)存中的變量,這樣就導致了問題的產(chǎn)生,也就導致了線程安全問題。

5.指令重排序

指令重排序是指 Java 程序為了提高程序的執(zhí)行速度,所以會對一下操作進行合并和優(yōu)化的操作。比如說,張三要去圖書館還書,舍友又讓張三幫忙借書,那么程序的執(zhí)行思維是,張三先去圖書館把自己的書還了,再去一趟圖書館幫舍友把書借回來。而指令重排序之后,把兩次執(zhí)行合并了,張三帶著自己的書去圖書館把書先還了,再幫舍友把書借出來,整個流程就執(zhí)行完了,這是正常情況下的指令重排序的好處。但是指令重排序也有“副作用”,而“副作用”是發(fā)生在多線程執(zhí)行中的,還是以張三借書和幫舍友還書為例,如果張三是一件事做完再做另一件事是沒有問題的(也就是單線程執(zhí)行是沒有問題的),但如果是多線程執(zhí)行,就是兩件事由多個人混合著做,比如張三在圖書館遇到了自己的多個同學,于是就把任務分派給多個人一起執(zhí)行,有人借了幾本書、有人借了還了幾本書、有人再借了幾本書、有人再借了還了幾本書,執(zhí)行的很混亂沒有明確的目標,到最后悲劇就發(fā)生了,這就是在指令重排序帶來的線程安全問題。

總結(jié)

線程安全是指某個方法或某段代碼,在多線程中能夠正確的執(zhí)行,不會出現(xiàn)數(shù)據(jù)不一致或數(shù)據(jù)污染的情況,反之則為線程安全問題。簡單來說所謂的非線程安全是指:在多線程中,程序的執(zhí)行結(jié)果和預期的正確結(jié)果不一致的問題。而造成線程安全問題的因素有 5 個:多線程搶占式執(zhí)行、多線程同時修改同一個變量、非原子性操作、內(nèi)存可見性和指令重排序。


分享標題:面試突擊:線程安全問題是如何產(chǎn)生的?
文章轉(zhuǎn)載:http://m.5511xx.com/article/cccghgh.html