亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

多線程中內存一致性錯誤的真實示例?

多線程中內存一致性錯誤的真實示例?

子衿沉夜 2023-05-24 14:35:51
在java多線程的教程中,給出了一個Memory Consistency Errors的例子。但我無法重現它。還有其他方法可以模擬內存一致性錯誤嗎?教程中提供的示例:假設定義并初始化了一個簡單的 int 字段:int?counter?=?0;計數器字段在兩個線程 A 和 B 之間共享。假設線程 A 遞增計數器:counter++;然后,不久之后,線程 B 打印出計數器:System.out.println(counter);如果這兩個語句在同一個線程中執行,則可以安全地假設打印出的值是“1”。但是,如果這兩個語句在不同的線程中執行,則打印出的值很可能是“0”,因為不能保證線程 A 對計數器的更改對線程 B 可見——除非程序員在兩者之間建立了 happens-before 關系這兩個聲明。
查看完整描述

5 回答

?
慕村225694

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。


查看完整回答
反對 回復 2023-05-24
?
牧羊人nacy

TA貢獻1862條經驗 獲得超7個贊

這可能會重現問題,至少在我的電腦上,我可以在一些循環后重現它。

  1. 假設你有一個Counter類:

    class Holder {
        boolean flag = false;
            long modifyTime = Long.MAX_VALUE;
    }
  2. 設為thread_A,flagtrue時間存入 modifyTime

  3. 讓另一個線程,比方說thread_B,閱讀Counterflag。如果晚于thread_B仍然get even ,那么我們可以說我們已經重現了問題。falsemodifyTime

示例代碼

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


查看完整回答
反對 回復 2023-05-24
?
慕碼人2483693

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 就足夠了


查看完整回答
反對 回復 2023-05-24
?
躍然一笑

TA貢獻1826條經驗 獲得超6個贊

請再看看你的源代碼中是如何介紹這個例子的。

避免內存一致性錯誤的關鍵是理解 happens-before 關系。這種關系只是保證一個特定語句對內存的寫入對另一個特定語句可見。要看到這一點,請考慮以下示例。

這個例子說明了多線程不是確定性的,因為你不能保證不同線程操作的執行順序,這可能會導致多次運行的不同觀察結果。但是并不能說明內存一致性錯誤!

要了解什么是內存一致性錯誤,您需要首先了解內存一致性。Lamport 在 1979 年引入了最簡單的內存一致性模型。這是原始定義。

任何執行的結果都是一樣的,就好像所有進程的操作都按某種順序執行,并且每個進程的操作都按照其程序指定的順序出現在這個序列中

現在,考慮這個示例多線程程序,請看一下最近一篇關于順序一致性的研究論文中的這張圖片。它說明了真正的內存一致性錯誤可能是什么樣子。

http://img4.sycdn.imooc.com/646db0ad00015dac04930340.jpg

要最終回答您的問題,請注意以下幾點:

  1. 內存一致性錯誤始終取決于底層內存模型(特定的編程語言可能允許更多行為以進行優化)。什么是最好的內存模型仍然是一個懸而未決的研究問題。

  2. 上面給出的例子給出了一個違反順序一致性的例子,但是不能保證你可以用你喜歡的編程語言觀察到它,原因有兩個:它取決于編程語言精確的內存模型,并且由于不確定性,你沒有強制執行特定錯誤執行的方法。

查看完整回答
反對 回復 2023-05-24
?
qq_笑_17

TA貢獻1818條經驗 獲得超7個贊

有時當我試圖重現一些真正的并發問題時,我會使用調試器。在 print 上創建一個斷點,在 increment 上創建一個斷點并運行整個過程。釋放不同順序的斷點會得到不同的結果。

也許很簡單,但它對我有用。


查看完整回答
反對 回復 2023-05-24
  • 5 回答
  • 0 關注
  • 181 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號