1. 程式人生 > >多線程並發常見問題

多線程並發常見問題

兩個 參考 線程並發 font toupper 共享 .cn 獨立 中間

一 概述

1.volatile

保證共享數據一旦被修改就會立即同步到共享內存(堆或者方法區)中。

2.線程訪問堆中數據的過程

線程在棧中建立一個變量的副本,修改完畢後將數據同步到堆中。

3.指令重排

為了提高執行效率,CPU會將沒有依賴關系的指令重新排序。如果希望控制重新排序,可以使用volatile修飾一個變量,包含該變量的指令前後的指令各自獨立排序,前後指令不能交叉排序。

二 常見問題及應對

1.原子性問題

所謂原子性,指的是一個操作不可中斷,即在多線程並發的環境下,一個操作一旦開始,就會在同一個CPU時間片內執行完畢。如果同一個線程的多個操作在不同的CPU時間片上執行,由於中間出現停滯,後面的操作在執行時可能某個共

享數據被其他線程修改,而該修改並未同步到當前線程中,導致當前線程操作的數據與實際不符,這種由於執行不連貫導致的數據不一致問題被稱作原子性問題。

2.可見性問題

可見性問題的出現與線程訪問共享數據的方式有關。線程訪問堆(方法區)中的變量時,先在棧中建立一個變量的副本,修改後再同步到堆中。如果一個線程剛建立副本,這時另一線程修改了變量,尚未同步到堆中,這時就會出現兩個線程操作同一變量的同一種狀態的現象,比如i=9,變量i的初始值為9,每一個線程的操作都是減1。兩個線程A與B同時訪問變量,B先執行i-1,在將結果i=8同步到堆中前,A線程也執行i-1,這時i=9的狀態就被執行兩次,出現線程安全問題。


線程安全問題產生的原因:一個線程對共享數據的修改不能立即為其他線程所見。

volatile提供了一種解決方案:
一旦一個線程修改了被volatile修飾的共享數據,這種修改就會立即同步到堆中,這樣其他數據從堆中訪問共享數據時始終獲得的是在多個線程中的最新值。
volatile的缺陷:

volatile只能保證一個線程從堆中獲取數據時獲取的是當前所有線程中的最新值,假如一個線程已經從堆中復制了數據,在操作完成前,其他線程修改了數據,修改後的數據並不會同步到當前線程中。

3.有序性問題

為了提高執行效率,CPU會對那些沒有依賴關系的指令重新排序,重新排序後的執行結果與順序執行結果相同。


例如,在源代碼中:
int i=0;
int y=1;
CPU在執行時可能先執行“int y=1;”,接著執行“int i=0;”,執行結果與順序執行結果相同。
指令重排在單線程環境下是安全的,在多線程環境下就可能出現問題。比如:
線程A:

s=new String("sssss");//指令1
flag=false;//指令2

線程B:

while(flag){
doSome();
}
s.toUpperCase();//指令3

如果線程A順序執行,即執行指令1,再執行指令2,線程B的執行不會出現問題。指令重排後,假如線程A先執行指令2,
這是flag=true,切換到線程2,終止循環,執行指令3,由於s對象尚未創建就會出現空指針異常。
有序性問題產生的原因:

一個線程對其他線程對共享數據的修改操作有順序要求,比如線程B要求線程A先執行指令1,再執行指令2,由於指令重排,實際並未按照要求的順序執行,這時就出現了線程安全問題。

解決思路:

  1. 利用同步機制,使得同一時間只有一個線程可以訪問共享數據,效率低。
  2. 使用volatile,一個指令包含volatile修飾的變量,那麽這條指令的執行順序不變,該指令前後的指令可以各自獨立重排,無法交叉重排。

參考:

http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

http://www.cnblogs.com/dolphin0520/p/3920373.html

多線程並發常見問題