多線程併發常見問題

多線程併發常見問題

一 概述

1.volatile

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

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的狀態就被執行兩次,出現線程安全問題。
線程安全問題產生的緣由:一個線程對共享數據的修改不能當即爲其餘線程所見。spa

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

volatile只能保證一個線程從堆中獲取數據時獲取的是當前全部線程中的最新值,假如一個線程已經從堆中複製了數據,在操做完成前,其餘線程修改了數據,修改後的數據並不會同步到當前線程中。指針

3.有序性問題

爲了提升執行效率,CPU會對那些沒有依賴關係的指令從新排序,從新排序後的執行結果與順序執行結果相同。
例如,在源代碼中:htm

int i=0; int y=1;對象

CPU在執行時可能先執行「int y=1;」,接着執行「int i=0;」,執行結果與順序執行結果相同。
指令重排在單線程環境下是安全的,在多線程環境下就可能出現問題。好比:
線程A:blog

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修飾的變量,那麼這條指令的執行順序不變,該指令先後的指令能夠各自獨立重排,沒法交叉重排。
相關文章
相關標籤/搜索