[英]Object Sharing in Simple Multi-threaded Program
我寫了一個非常簡單的程序,試圖將自己重新引入JAVA中的多線程編程。 我的計划的目標是來源於此 ,而整潔的系列文章,由雅各布Jankov寫的。 有關程序的原始未修改版本,請參閱鏈接文章的底部。
Jankov的程序沒有System.out.println
變量,因此您看不到正在發生什么。 如果您.print
結果值,則每次都會得到相同的結果(程序是線程安全的); 但是,如果您打印一些內部工作原理,則每次的“內部行為”都是不同的。
我了解線程調度中涉及的問題以及線程的Running
的不可預測性。 我認為這可能是我下面提出的問題的一個因素。
主類:
public class multiThreadTester {
public static void main (String[] args) {
// Counter object to be shared between two threads:
Counter counter = new Counter();
// Instantiation of Threads:
Thread counterThread1 = new Thread(new CounterThread(counter), "counterThread1");
Thread counterThread2 = new Thread(new CounterThread(counter), "counterThread2");
counterThread1.start();
counterThread2.start();
}
}
上一類的目的僅僅是共享一個對象。 在這種情況下,線程共享一個Counter
類型的對象:
櫃台類
public class Counter {
long count = 0;
// Adding a value to count data member:
public synchronized void add (long value) {
this.count += value;
}
public synchronized long getValue() {
return count;
}
}
上面僅是Counter
類的定義,該類僅包含long
類型的基本成員。
CounterThread類
下面是CounterThread
類,它實際上未從Jankov提供的代碼中進行修改。 唯一真正的區別(盡管我實現了 Runnable
而不是擴展 Thread
)是System.out.println()
。 我添加了它以觀察程序的內部工作原理。
public class CounterThread implements Runnable {
protected Counter counter = null;
public CounterThread(Counter aCounter) {
this.counter = aCounter;
}
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("BEFORE add - " + Thread.currentThread().getName() + ": " + this.counter.getValue());
counter.add(i);
System.out.println("AFTER add - " + Thread.currentThread().getName() + ": " + this.counter.getValue());
}
}
}
如您所見,代碼非常簡單。 上面的代碼的唯一目的是觀察兩個線程共享一個線程安全對象時發生的情況。
我的問題來自程序輸出的結果(我在下面嘗試進行了總結)。 輸出很難“保持一致”以證明我的問題,因為差異的擴散(請參見下文)可能非常大:
這是壓縮的輸出(試圖將您的外觀最小化):
AFTER add - counterThread1: 0
BEFORE add - counterThread1: 0
AFTER add - counterThread1: 1
BEFORE add - counterThread1: 1
AFTER add - counterThread1: 3
BEFORE add - counterThread1: 3
AFTER add - counterThread1: 6
BEFORE add - counterThread1: 6
AFTER add - counterThread1: 10
BEFORE add - counterThread2: 0 // This BEFORE add statement is the source of my question
還有一個更好地演示的輸出:
BEFORE add - counterThread1: 0
AFTER add - counterThread1: 0
BEFORE add - counterThread1: 0
AFTER add - counterThread1: 1
BEFORE add - counterThread2: 0
AFTER add - counterThread2: 1
BEFORE add - counterThread2: 1
AFTER add - counterThread2: 2
BEFORE add - counterThread2: 2
AFTER add - counterThread2: 4
BEFORE add - counterThread2: 4
AFTER add - counterThread2: 7
BEFORE add - counterThread2: 7
AFTER add - counterThread2: 11
BEFORE add - counterThread1: 1 // Here, counterThread1 still believes the value of Counter's counter is 1
AFTER add - counterThread1: 13
BEFORE add - counterThread1: 13
AFTER add - counterThread1: 16
BEFORE add - counterThread1: 16
AFTER add - counterThread1: 20
我的問題:
線程安全性確保變量的安全可變性,即一次只能有一個線程可以訪問一個對象。 這樣做可以確保“讀取”和“寫入”方法僅在線程釋放其鎖定(消除競速)后才能正確執行操作。
為什么盡管寫行為正確, counterThread2
“相信” Counter
的值( 而不是迭代器 i
)仍為零? 內存中正在發生什么? 這是否是包含它自己的本地Counter
對象的線程的問題?
或者,更簡單地說,在counterThread1
更新值之后,為什么counterThread2
看不到正確的值System.out.println()
在本例中為System.out.println()
? 盡管看不到該值 ,但正確的值已寫入對象。
為什么盡管寫行為正確,counterThread2是否仍“相信” Counter的值仍為零?
線程以導致這種行為的方式交錯。 由於打印語句位於同步塊之外,因此線程可能會讀取計數器值,然后由於調度而暫停,而另一個線程會多次遞增。 當等待線程最終恢復運行並進入inc計數器方法時,計數器的值將繼續移動很多,不再與BEFORE日志行中打印的內容匹配。
例如,我修改了您的代碼,以使兩個線程都在同一計數器上工作更加明顯。 首先,我將打印語句移到了計數器中,然后添加了一個唯一的線程標簽,以便我們可以知道哪個線程負責增量,最后我只增加了一個,以便計數器值的任何跳躍都將更加明顯。
public class Main {
public static void main (String[] args) {
// Counter object to be shared between two threads:
Counter counter = new Counter();
// Instantiation of Threads:
Thread counterThread1 = new Thread(new CounterThread("A",counter), "counterThread1");
Thread counterThread2 = new Thread(new CounterThread("B",counter), "counterThread2");
counterThread1.start();
counterThread2.start();
}
}
class Counter {
long count = 0;
// Adding a value to count data member:
public synchronized void add (String label, long value) {
System.out.println(label+ " BEFORE add - " + Thread.currentThread().getName() + ": " + this.count);
this.count += value;
System.out.println(label+ " AFTER add - " + Thread.currentThread().getName() + ": " + this.count);
}
public synchronized long getValue() {
return count;
}
}
class CounterThread implements Runnable {
private String label;
protected Counter counter = null;
public CounterThread(String label, Counter aCounter) {
this.label = label;
this.counter = aCounter;
}
public void run() {
for (int i = 0; i < 10; i++) {
counter.add(label, 1);
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.