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

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

多線程如何與 Java 中的循環一起工作?

多線程如何與 Java 中的循環一起工作?

叮當貓咪 2023-06-21 13:59:34
當我使用 2 個線程調用同一對象 (new Counter()) 的方法時,while 循環運行的次數超過了循環限制。有人可以用簡單的語言解釋相同的原因。主要課程:public class MainClass {public static void main(String[] args) {    Counter c1 = new Counter();    MyRunnable r1 = new MyRunnable(c1);    Thread t1 = new Thread(r1, "t1");    Thread t2 = new Thread(r1, "t2");    t1.start();    t2.start();}}可運行:public class MyRunnable implements Runnable {Counter counter;public MyRunnable(Counter counter) {    this.counter = counter;}@Overridepublic void run() {    counter.add();}}柜臺類:public class Counter {    int i = 1;    public void add() {        while (i <= 5) {            System.out.println(i + " " + Thread.currentThread().getName());            i++;        }    }}輸出 :1 t11 t22 t13 t24 t15 t2我想知道,如果限制是 5,那為什么輸出 6 個值。有時它也輸出5個值?如果有人對此有所了解,那就太好了。謝謝。
查看完整描述

4 回答

?
浮云間

TA貢獻1829條經驗 獲得超4個贊

您的代碼存在兩個并發問題。

  1. 對于并發任務,共享 inti既沒有適當聲明也沒有訪問。它至少需要是易失性的,但為了正確處理原子增量應該是一個AtomicInteger. Volatile 將有助于保證其他線程可以看到值是否發生變化(JVM 無法保證這一點),但原子整數更進一步,它提供了原子操作,這才是您真正需要的。

  2. 您的循環可以同時被兩個線程訪問,因為條件檢查與變量的增量是分開的。要正確處理運行循環的并發線程,您需要while此處的循環條件也將增量作為單個原子操作執行。或者你需要添加一個同步塊。

也許嘗試類似下面未經測試的代碼:

public class Counter {

    private final AtomicInteger i = new AtomicInteger(1);


    public void add() {

        for (;;)

        {

            int localCount = i.getAndIncrement();

            if (localCount > 5) { break; }


            System.out.println(localCount + " " + Thread.currentThread().getName());

        }

    }

}

逐行解釋一下上面的代碼:

  • 我們切換i到 an AtomicInteger,因為它允許我們獲取當前值并自動將下一個值加一。這種原子性很重要,因為否則兩個競爭線程可能會增加到相同的值,而不是在所有線程中讀取和增加每個值恰好一次。我們可以通過將代碼的遞增部分放入同步塊中來實現相同的目的。

  • 我們切換到一個for (;;)循環,以便我們可以將循環控制/終止代碼移動到循環體內。這使我們能夠輕松地將原子增量的結果用于控制代碼和循環體內(例如在語句中println)。

  • i.getAndIncrement()原子地增加我們的計數器并返回給我們增加之前的值。無論有多少線程在運行,都可以保證此操作有效,并確保我們不會遺漏或重復任何數字。將先前的計數值分配給localCountint 為我們提供了在循環中使用的值。如果我們嘗試i在循環內重新訪問而不是使用 localCount,那么我們最終會在某個時刻得到不同的數字,因為其他線程正在同時遞增i。

  • 我們檢查localCount它是否超出了我們的終止限制,如果超過了,我們就中斷循環。我們不檢查,i因為它可能已被另一個線程修改,并且我們只關心該線程當前的數量是否超出限制。

  • 最后,我們使用localCountinstead of 來執行 println i,因為i在代碼到達此處時可能已被另一個線程修改(這將導致打印重復行)。

請注意,此代碼不保證計數器的數字將按順序打印。 很可能大多數時候都是這種情況,但肯定不能保證也不應該依賴它。您的問題沒有說明實際的預期/要求的結果,如果這是一項要求,那么您很可能需要圍繞增量和 println 語句進行同步,以確保不會亂序執行。

例如同步解決方案:

public class Counter {

    int i = 1;


    public void add() {

        for (;;) {

            synchronized(this) {

                if (i > 5) { break; }

                System.out.println(i + " " + Thread.currentThread().getName());

                i++;

            }

        }

    }

}


查看完整回答
反對 回復 2023-06-21
?
慕標5832272

TA貢獻1966條經驗 獲得超4個贊

這是個簡單的。

  1. 您創建了一個名為c1;的計數器

  2. 您從此計數器創建了一個 Runnable;

  3. 您將同一個可運行對象傳遞給了兩個線程。兩個線程現在將在同一個 Counter 對象上運行c1;

  4. 您在方法內運行 while 循環add()。

add()方法在一行上打印一些內容,并在下一行上遞增i。


對于您當前的輸出,在第一個線程執行其 print 語句后,它被中斷,第二個線程獲得了 CPU 的控制權。第二個線程設法執行了它的打印。然后任一線程遞增計數器(我們不知道是哪一個),并且打印和計數器遞增兩個步驟繼續。


查看完整回答
反對 回復 2023-06-21
?
BIG陽

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

問題是您正在i同時訪問該變量。Java 將變量緩存在 CPU 緩存中。由于每個線程都可以在不同的內核中執行,因此每個線程中的值i可能不同。即使您正在執行i++底層代碼也是i = i + 1. 您正在讀取變量,然后正在寫入,因此在這種情況下多個線程也可能具有不同的值。

這個問題的解決方案是像你的情況一樣使用Atomic變量。AtomicInteger這些原子變量是線程安全的,并且像在事務中一樣讀寫,在更新變量時阻塞對線程的訪問。

在您的情況下,int您可以聲明而不是聲明

    private volatile AtomicInteger atomicInteger = new AtomicInteger(1);

而不是使用i++你可以使用atomicInteger.addAndGet(1)


查看完整回答
反對 回復 2023-06-21
?
當年話下

TA貢獻1890條經驗 獲得超9個贊

您剛剛發現的是競爭條件線程可以counter不受任何限制地訪問對象。即使i是 int,也不能保證,例如,當“t2”讀取該值時,“t1”完成了該值的遞增。嘗試運行該程序幾次,輸出可能會有所不同。

要解決你的問題,你需要實現一種鎖,或者使用關鍵字synchronized。一般來說,在實現使用多線程的應用程序時,您需要識別關鍵部分并確保避免競爭條件。

編輯:我不敢相信我寫的protected不是synchronized,不知道這是怎么發生的。就在今天偶然發現了這個,編輯所以沒有其他人被誤導。


查看完整回答
反對 回復 2023-06-21
  • 4 回答
  • 0 關注
  • 219 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

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

幫助反饋 APP下載

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

公眾號

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