什么是執行緒安全問題
多執行緒同時訪問一個資源,且其中至少有一個執行緒對這個資源進行了修改,導致其他的執行緒訪問到的與期望的不一致這就是執行緒安全問題,只讀不修改不存在執行緒安全問題,
執行緒安全問題產生的必要條件
1.多個執行緒同時訪問一個資源 2.存在賦值操作 3.被訪問的資源被所有執行緒共享
怎么避免執行緒安全問腿
我們創建十個執行緒,每個執行緒都對i進行自加10000次,最后輸出的結果,應該是100001
private static int i=1;
public static void main(String[] args) throws InterruptedException {
for(int m=0;m<=9;m++){
new TestMyThread().start();
}
Thread.sleep(500);
System.out.println(i);
}
static class TestMyThread extends Thread{
@Override
public void run() {
for(int n=0;n<10000;n++){
i++;
}
System.out.println(Thread.currentThread().getName()+"執行緒結束了");
}
}
經測,每次輸出結果都小于100001
那么用volatile關鍵字修飾共享變數有用嗎?

測驗發現結果還是不盡人意,
volatile有兩個特點:
1.防止指令重排序
2.強制從主記憶體中讀取資料,而不是本地記憶體
所以volatile只能保障變數的可見性,但是不能保障變數賦值的原子性,
這時候我給出兩種解決辦法:
第一種:使用synchronized加鎖來實作執行緒同步
private static int i=1;
private static Object object=new Object();
public static void main(String[] args) throws InterruptedException {
for(int m=0;m<=9;m++){
new TestMyThread().start();
}
Thread.sleep(500);
System.out.println(i);
}
static class TestMyThread extends Thread{
@Override
public void run() {
synchronized (object){
for(int n=0;n<10000;n++){
i++;
}
System.out.println(Thread.currentThread().getName()+"執行緒結束了");
}
}
}
synchronized:鎖靜態變數和鎖實體方法,鎖this是一樣的,鎖靜態方法和鎖類是一樣的,
第二種:
這時候我們可以使用java給我們提供的專門對整數進行并發處理的原子類AtomicInteger,該類在java.util.concurrent.atomic包下,來試試效果把

如果,我們只是用普通的i++這種操作在高并發的情況下是非常危險的,進行一次i++分為三個步驟:
1.先獲取舊值
2.再進行加一
3.再將新值賦給舊值
所以就可能出現加一后還沒賦值,被其他執行緒讀取了舊的值,也進行了加一,最后必有一個重復賦值的,這也就解釋了,為什么使用volatile還是存在執行緒安全問題的原因,
通過原子類,可以讓自加操作變為一個原子操作,底層其實就是修改值后,再和期望的舊值比較是否相等,如果相等就更新,不相等的話就再進行一次操作直到相等為止,(CAS演算法)
手動實作CAS演算法:
public static void main(String[] args) throws InterruptedException {
CasCount casCount = new CasCount();
//創建10001個執行緒對i進行自加操作
for(int m=0;m<=10000;m++){
new Thread(new Runnable() {
@Override
public void run() {
casCount.getAndIncrement();
}
}).start();
}
Thread.sleep(1000);
System.out.println(casCount.getI());
}
static class CasCount{
volatile static private int i=0;
public int getI() {
return i;
}
private boolean compareAndSwap(int expectValue, int newValue){
synchronized (this){
if(expectValue==i){
i=newValue;
return true;
}else {
return false;
}
}
}
public int getAndIncrement(){
int oldValue;
int newValue;
do{
oldValue=getI();
newValue=oldValue+1;
}while (!compareAndSwap(oldValue,newValue));
return newValue;
}
}
CAS演算法只能保證值和我們期望的一致,因為即使得到了我們想要的值,也可能被多個執行緒修改過,這也是CAS演算法的缺點(ABA問題),但是我們可以創建一個版本號,來記錄下每次對變數的修改,每修改一次,便加一,這樣就能通過控制版本號保障來每次只被一個執行緒修改過啦,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/282253.html
標籤:其他
