簡體   English   中英

Java 多線程意外 output

[英]Java multithreading unexpected output

我正在嘗試在 Java 中學習多線程。 這是我寫的代碼:

public class SharedResources {
    volatile private int sharedPar1;
    volatile private String sharedPar2;

    public SharedResources(Integer sharedPar1, String sharedPar2) {
        synchronized (sharedPar1) {
            this.sharedPar1 = sharedPar1;
        }
        synchronized (sharedPar2) {
            this.sharedPar2 = sharedPar2;
        }
    }

    public synchronized int getSharedPar1() {
        return sharedPar1;
    }

    public synchronized void setSharedPar1(int sharedPar1) {
        this.sharedPar1 = sharedPar1;
    }

    public synchronized String getSharedPar2() {
        return sharedPar2;
    }

    public synchronized void setSharedPar2(String sharedPar2) {
        this.sharedPar2 = sharedPar2;
    }
}

上面這只是一個代表模擬共享資源的class。 兩個模擬線程類:

public class Task1 extends Thread {
    volatile private SharedResources sharedResources;

    public Task1(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    public SharedResources getSharedResources() {
        return sharedResources;
    }

    public void setSharedResources(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            sharedResources.setSharedPar1(sharedResources.getSharedPar1() + 1000);
            sharedResources.setSharedPar2(sharedResources.getSharedPar2() + "c");
            System.out.println("Task1, it. " + i + ". - Shared resources: " + sharedResources.getSharedPar1() + " and " + sharedResources.getSharedPar2());
        }
    }
}

任務 2:

public class Task2 extends Thread {
    volatile private SharedResources sharedResources;

    public Task2(SharedResources sharedResource) {
        this.sharedResources = sharedResource;
    }

    public SharedResources getSharedResources() {
        return sharedResources;
    }

    public void setSharedResources(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            sharedResources.setSharedPar1(sharedResources.getSharedPar1() + 100);
            sharedResources.setSharedPar2(sharedResources.getSharedPar2() + "b");
            System.out.println("Task2, it. " + i + ". - Shared resources: " + sharedResources.getSharedPar1() + " and " + sharedResources.getSharedPar2());
        }
    }
}

現在,主要的function:

public static void main(String[] args) {
        SharedResources sharedResource = new SharedResources(0, "");

        Thread firstTask = new Task1(sharedResource);
        Thread secondTask = new Task2(sharedResource);

        firstTask.start();
        secondTask.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("Main thread! - Shared resources: " + sharedResource.getSharedPar1() + " and " + sharedResource.getSharedPar2());
            sharedResource.setSharedPar1(sharedResource.getSharedPar1() + 1);
            sharedResource.setSharedPar2(sharedResource.getSharedPar2() + "a");
        }
        System.out.println();
    }

現在,想法是這樣 - 有兩個線程對變量sharedPar1sharedPar2進行不同的操作,在 output 我想獲得變量的一致值,這意味着我希望能夠將該程序的 output 邏輯連接到它的代碼行。 但是,我不知道為什么我的 output 中有一些東西。 基本上,我需要有人向我解釋這種output:

Main thread! - Shared resources: 1000 and 
Main thread! - Shared resources: 1101 and ba
Main thread! - Shared resources: 1102 and baa
Main thread! - Shared resources: 1103 and baaa
Main thread! - Shared resources: 1104 and baaaa
Main thread! - Shared resources: 1105 and baaaaa
Main thread! - Shared resources: 1106 and baaaaaa
Main thread! - Shared resources: 1107 and baaaaaaa
Main thread! - Shared resources: 1108 and baaaaaaaa
Main thread! - Shared resources: 1109 and baaaaaaaaa

Task2, it. 0. - Shared resources: 1100 and b
Task1, it. 0. - Shared resources: 1100 and b
Task2, it. 1. - Shared resources: 1210 and baaaaaaaaaab
Task1, it. 1. - Shared resources: 2210 and baaaaaaaaaabc
Task1, it. 2. - Shared resources: 3310 and baaaaaaaaaabcc
Task2, it. 2. - Shared resources: 3310 and baaaaaaaaaabcb
Task1, it. 3. - Shared resources: 4310 and baaaaaaaaaabcbc
Task2, it. 3. - Shared resources: 4410 and baaaaaaaaaabcbcb
Task1, it. 4. - Shared resources: 5410 and baaaaaaaaaabcbcbc
Task2, it. 4. - Shared resources: 5510 and baaaaaaaaaabcbcbcb
Task1, it. 5. - Shared resources: 6510 and baaaaaaaaaabcbcbcbc
Task2, it. 5. - Shared resources: 6610 and baaaaaaaaaabcbcbcbcb
Task1, it. 6. - Shared resources: 7610 and baaaaaaaaaabcbcbcbcbc
Task2, it. 6. - Shared resources: 7710 and baaaaaaaaaabcbcbcbcbcb
Task1, it. 7. - Shared resources: 8710 and baaaaaaaaaabcbcbcbcbcbc
Task1, it. 8. - Shared resources: 9810 and baaaaaaaaaabcbcbcbcbcbcc
Task1, it. 9. - Shared resources: 10810 and baaaaaaaaaabcbcbcbcbcbccc
Task2, it. 7. - Shared resources: 9810 and baaaaaaaaaabcbcbcbcbcbcb
Task2, it. 8. - Shared resources: 10910 and baaaaaaaaaabcbcbcbcbcbcccb
Task2, it. 9. - Shared resources: 11010 and baaaaaaaaaabcbcbcbcbcbcccbb

我了解 output 中的所有內容,直到Task2 行。 0. - 共享資源:1100 和 b - 為什么在這一行中我們沒有前一行的值(主循環中的最后一行)? Output 的主要部分,我們在字符串中添加了“aaa..”,而添加到 integer 變量中的那些在這一行中找不到,這就是我不明白的。

所以,基本上我希望能夠在多個不同線程之間使用共享的 class 和變量,但即使我將變量設置為“易失性”並將方法設置為“同步”,輸出到屏幕時再次無法正確更新值。 有人可以解釋這是為什么嗎? 如果你發現這是做這些事情的錯誤方式,我錯在哪里?

另外,我想知道是否有一種方法可以確保在一個線程中,我們總是執行一個循環的完整迭代,然后調度程序將我們的線程踢出處理器?

有幾個問題。 首先,您在獲取/設置周期上有一個競爭條件,以便每個線程可以讀取相同的初始值,因此它們替換另一個線程的更新值。 這是因為您沒有在整個塊上同步每個獨立變量的原子更改,並且兩個線程在每個運行sharedResource.setSharedParX(updatedValue) ) 之前執行getSharedParX() ) :

sharedResource.setSharedParX(sharedResource.getSharedParX() + x);

因此,您的讀取/更新時間線會踐踏其他線程的結果(請注意,在更新之前主要打印),並且這些線程的 output 的第一行使用sharedPar1sharedPar2的第一個/第二個值的混合:

// Task1 read sharedPar1=0 and saved sharedPar1=1000
Main thread! - Shared resources: 1000 and 

// Task2 reads sharedPar1=1000 and saved sharedPar1=1100 (trampling main values)
// Task2 read sharedPar2="" and saved sharedPar2="b" (trampling main/Task2 values)
Task2, it. 0. - Shared resources: 1100 and b
Task1, it. 0. - Shared resources: 1100 and b

其次注意System.out是一個PrintWriter所以println消息是同步的。 在 Task1/Task2 有機會之前,主線程能夠打印更多結果,這就是為什么 Task1/2 的第一行出現在列表中的原因。 在線程內部打印到相同的 stream 通常不是一個好主意,因為 I/O 爭用可能會減慢所有線程,因為它們相互競爭以寫入 output。

您需要修復以使操作是原子的 - 查看使用AtomicIntegerAtomicReference<String>或實現您自己的處理程序以執行一步所需的更改(並刪除 setX),例如:

synchronized int addAndGet(int delta) {
    sharedPar1 += delta;
    return sharedPar1;
}
synchronized String appendAndGet(String delta) {
    sharedPar2 += delta;
    return sharedPar2;
}

暫無
暫無

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

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