簡體   English   中英

變量'runner'在循環內不更新

[英]Variable 'runner' is not updated inside loop

像這樣,我有兩個線程。 SleepRunner 線程將一些隨機數添加到列表中,然后將標志更改為 true 並進入睡眠狀態。 主線程等待 SleepRunner 線程,直到 SleepRunner object 中的標志由 false 變為 true,然后主線程將中斷 SleepRunner 線程,程序結束。

但問題是,當 while 循環在主線程中沒有主體代碼時,變量 'runner' 不會在循環內更新,換句話說 SleepRunner 線程將標志從 false 更改為 true 后程序沒有結束。 於是嘗試在idea中使用debug工具,程序順利結束。 如果我在主線程的 while 循環體中編寫一些代碼,如 System.out.println() 或 Thread.sleep(1) ,程序也成功結束。 太不可思議了? 有誰知道為什么會這樣。 謝謝。

public class Test1 {
        public static void main(String[] args) {
            SleepRunner runner = new SleepRunner();
            Thread thread = new Thread(runner);
            thread.start();
            while(!(runner.isFlag())){
                /*try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
            System.out.println("END");
            thread.interrupt();
        }
    }

public class SleepRunner implements Runnable {
        private boolean flag = false;
    
        public boolean isFlag() {
            return flag;
        }
    
        @Override
        public void run() {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep((long) (Math.random() * 200));
                }
                catch (InterruptedException e) {
                    System.out.println("Interrupted");
                }
                int num = (int) (Math.random() * 100);
                System.out.println(Thread.currentThread().getName() + " " + num);
                list.add(num);
            }
    
            flag = true;
    
            System.out.println("30 Seconds");
            try {
                Thread.sleep(30000);
            }
            catch (InterruptedException e) {
                System.out.println("Interrupted in 30 seconds");
            }
            System.out.println("sleep runner thread end");
        }
    }

你違反了 java memory model。

以下是 JMM 的工作原理*:

每當讀取更新任何字段(來自任何對象)時,每個線程都會拋硬幣。 在頭上,它將制作一個副本並從中更新/讀取。 在尾巴上,它不會。 您的工作是確保您的代碼能夠正常運行,而不管硬幣是如何落地的,並且您不能在單元測試中強制拋硬幣。 硬幣不必是“公平的”。 硬幣的行為取決於音樂播放器中播放的音樂、幼兒的奇思妙想以及月相。 (換句話說,任何更新/讀取都可以對本地緩存副本進行,也可以不進行,直到 java 實現)。

您可以安全地得出結論,正確執行此操作的唯一方法是確保線程永遠不會拋硬幣。

實現這一目標的方法是建立所謂的“先於”關系。 建立它們主要是通過使用同步原語,或通過調用使用同步原語的方法來完成的。 例如,如果我這樣做:

線程 X:

synchronized(x) {
    x.foo();
    System.out.println(shared.y);
    shared.y = 10;
}

線程 Y:

synchronized(x) {
    x.foo();
    System.out.println(shared.y);
    shared.y = 20;
}

那么您已經建立了一種關系:代碼塊 A 在代碼塊 B 之前出現,反之亦然,但您至少已經確定它們必須按順序運行。

因此,這將保證打印0 100 20 沒有同步塊,它也可以合法地打印0 0 所有 3 個結果都是可接受的結果(java 語言規范說沒問題,並且您認為這沒有意義的任何錯誤都將被視為“按預期工作”)。

也可以使用volatile ,但 volatile 的作用相當有限。

通常,由於無法對其進行充分測試,因此在 java 中只有 3 種方法可以正確執行線程:

  1. 'in the large':使用網絡服務器或其他負責多線程的應用程序框架。 您不編寫psv main()方法,該框架編寫,您編寫的只是“處理程序”。 您的處理程序根本沒有接觸任何共享數據。 處理程序要么不共享數據,要么通過旨在正確執行數據的總線共享數據,例如可序列化事務隔離模式下的數據庫,或 rabbitmq 或其他消息總線。
  2. 'in the small':使用 fork/join 並行化一個巨大的任務。 當然,任務的處理程序不能使用任何共享數據。
  3. 閱讀 Concurrency in Practice(書籍),更喜歡使用 java.util.concurrent package 中的類,並且通常成為了解這些東西如何工作的大師,因為以任何其他方式進行線程處理可能會導致您編程錯誤,而您測試可能不會捕獲,但要么會在生產時崩潰,要么會導致沒有實際的多線程(例如,如果你過度同步所有東西,你最終會得到除了一個核心之外的所有核心都在等待,你的代碼實際上會運行比單線程慢得多)。

*) 完整的解釋是關於一本書的價值。 我只是給你過度簡化的亮點,因為這只是一個 SO 答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM