简单多线程程序中的对象共享

时间:2014-11-09 16:42:59

标签: java multithreading

简介

我编写了一个 very 简单程序,试图将自己重新介绍给JAVA中的多线程编程。我的课程的目标来自于this相当简洁的文章,由Jakob 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 Class

下面是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() - 正确的值? 尽管没有看到值,但是将正确的值写入对象。

1 个答案:

答案 0 :(得分:1)

  

尽管有正确的写行为,但为什么counterThread2&#34;相信&#34;计数器的值仍为零?

以这种方式交错的线程会导致此行为。因为打印语句在同步块之外,所以线程可以读取计数器值然后由于正在调度而暂停,而另一个线程递增多次。当等待线程最终恢复并进入inc计数器方法时,计数器的值将移动很多,并且将不再匹配BEFORE日志行中打印的值。

作为一个例子,我修改了你的代码,使两个线程在同一个计数器上运行更加明显。首先我将print语句移动到计数器中,然后我添加了一个唯一的线程标签,以便我们可以告诉哪个线程负责增量,最后我只增加一个,这样计数器值中的任何跳转都会更清楚地突出

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);
        }
    }
}