5 回答

TA貢獻1880條經驗 獲得超4個贊
鑒于這段代碼:
public class Test {
? ? volatile static private int a;
? ? static private int b;
? ? public static void main(String [] args) throws Exception {
? ? ? ? for (int i = 0; i < 100; i++) {
? ? ? ? ? ? new Thread() {
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? ? ? int tt = b; // makes the jvm cache the value of b
? ? ? ? ? ? ? ? ? ? while (a==0) {
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if (b == 0) {
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("error");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }.start();
? ? ? ? }
? ? ? ? b = 1;
? ? ? ? a = 1;
? ? }
}
的易變存儲a發生在 的正常存儲之后b。因此,當線程運行并看到時a != 0,由于 JMM 中定義的規則,我們必須看到b == 1。
JRE 中的 bug 允許線程進入生產error線并隨后得到解決。如果您沒有a定義為,這肯定會失敗volatile。

TA貢獻1862條經驗 獲得超7個贊
這可能會重現問題,至少在我的電腦上,我可以在一些循環后重現它。
假設你有一個
Counter
類:class Holder { boolean flag = false; long modifyTime = Long.MAX_VALUE; }
設為
thread_A
,flag
將true
時間存入modifyTime
。讓另一個線程,比方說
thread_B
,閱讀Counter
的flag
。如果晚于thread_B
仍然get even ,那么我們可以說我們已經重現了問題。false
modifyTime
示例代碼
class Holder {
boolean flag = false;
long modifyTime = Long.MAX_VALUE;
}
public class App {
public static void main(String[] args) {
while (!test());
}
private static boolean test() {
final Holder holder = new Holder();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
holder.flag = true;
holder.modifyTime = System.currentTimeMillis();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
long lastCheckStartTime = 0L;
long lastCheckFailTime = 0L;
while (true) {
lastCheckStartTime = System.currentTimeMillis();
if (holder.flag) {
break;
} else {
lastCheckFailTime = System.currentTimeMillis();
System.out.println(lastCheckFailTime);
}
}
if (lastCheckFailTime > holder.modifyTime
&& lastCheckStartTime > holder.modifyTime) {
System.out.println("last check fail time " + lastCheckFailTime);
System.out.println("modify time " + holder.modifyTime);
return true;
} else {
return false;
}
}
}
結果
last check time 1565285999497
modify time 1565285999494
這意味著從提交的時間thread_B獲取,甚至將其設置為時間(早 3 毫秒)。falseCounterflag1565285999497thread_Atrue1565285999494

TA貢獻1860條經驗 獲得超9個贊
使用的示例太糟糕,無法證明內存一致性問題。讓它工作將需要脆弱的推理和復雜的編碼。然而,您可能無法看到結果。多線程問題是由于時機不巧而發生的。如果有人想增加觀察問題的機會,我們需要增加不幸時機的機會。以下程序實現了它。
public class ConsistencyIssue {
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Increment(), "Thread-1");
Thread thread2 = new Thread(new Increment(), "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter);
}
private static class Increment implements Runnable{
@Override
public void run() {
for(int i = 1; i <= 10000; i++)
counter++;
}
}
}
執行1輸出:10963,執行2輸出:14552
最終計數應該是 20000,但它比那個少。原因是count++是多步操作,1.讀count 2.increment count 3.store it
兩個線程可能會同時讀取計數 1,將其遞增到 2。然后寫出 2。但如果它是串行執行,則應該是 1++ -> 2++ -> 3。
我們需要一種方法使所有 3 個步驟成為原子。即一次只能由一個線程執行。
解決方案 1:Synchronized 用 Synchronized 包圍增量。由于計數器是靜態變量,您需要使用類級同步
@Override
public void run() {
for (int i = 1; i <= 10000; i++)
synchronized (ConsistencyIssue.class) {
counter++;
}
}
現在輸出:20000
解決方案 2:AtomicInteger
public class ConsistencyIssue {
static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Increment(), "Thread-1");
Thread thread2 = new Thread(new Increment(), "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.get());
}
private static class Increment implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10000; i++)
counter.incrementAndGet();
}
}
}
我們可以使用信號量,也可以使用顯式鎖定。但是對于這個簡單的代碼,AtomicInteger 就足夠了

TA貢獻1826條經驗 獲得超6個贊
請再看看你的源代碼中是如何介紹這個例子的。
避免內存一致性錯誤的關鍵是理解 happens-before 關系。這種關系只是保證一個特定語句對內存的寫入對另一個特定語句可見。要看到這一點,請考慮以下示例。
這個例子說明了多線程不是確定性的,因為你不能保證不同線程操作的執行順序,這可能會導致多次運行的不同觀察結果。但是并不能說明內存一致性錯誤!
要了解什么是內存一致性錯誤,您需要首先了解內存一致性。Lamport 在 1979 年引入了最簡單的內存一致性模型。這是原始定義。
任何執行的結果都是一樣的,就好像所有進程的操作都按某種順序執行,并且每個進程的操作都按照其程序指定的順序出現在這個序列中
現在,考慮這個示例多線程程序,請看一下最近一篇關于順序一致性的研究論文中的這張圖片。它說明了真正的內存一致性錯誤可能是什么樣子。
要最終回答您的問題,請注意以下幾點:
內存一致性錯誤始終取決于底層內存模型(特定的編程語言可能允許更多行為以進行優化)。什么是最好的內存模型仍然是一個懸而未決的研究問題。
上面給出的例子給出了一個違反順序一致性的例子,但是不能保證你可以用你喜歡的編程語言觀察到它,原因有兩個:它取決于編程語言精確的內存模型,并且由于不確定性,你沒有強制執行特定錯誤執行的方法。

TA貢獻1818條經驗 獲得超7個贊
有時當我試圖重現一些真正的并發問題時,我會使用調試器。在 print 上創建一個斷點,在 increment 上創建一個斷點并運行整個過程。釋放不同順序的斷點會得到不同的結果。
也許很簡單,但它對我有用。
添加回答
舉報