[英]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.