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

标签 java multithreading

简介

我编写了一个非常简单的程序,作为尝试重新介绍自己使用 JAVA 进行多线程编程。我的程序的目标源自this雅各布·扬科夫 (Jakob Jankov) 撰写的一组相当简洁的文章。对于该程序的原始、未经修改的版本,请参阅链接文章的底部。

Jankov 的程序没有 System.out.println 变量,因此您无法看到发生了什么。如果您 .print 结果值,每次都会得到相同的结果(程序是线程安全的);但是,如果您打印一些内部工作原理,则每次“内部行为”都会有所不同。

我了解线程调度所涉及的问题以及线程运行的不可预测性。我相信这可能是我在下面提出的问题的一个因素。

程序的三个部分

主类:

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())正确的值? 尽管没有看到该值,正确的值仍被写入对象。

最佳答案

Why, despite the correct write behaviour, does counterThread2 "believe" Counter's value to still be zero?

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

作为示例,我修改了您的代码,以便更明显地看出两个线程都在同一个计数器上工作。首先,我将打印语句移至计数器中,然后添加了一个唯一的线程标签,以便我们可以知道哪个线程负责增量,最后我只增量 1,以便计数器值中的任何跳跃都会更清晰地突出。

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

关于java - 简单多线程程序中的对象共享,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26830526/

相关文章:

java - 当队列已满时增加数组大小的入队方法

java - 在当前线程上模拟用户

java - FileDescriptor.sync() 线程安全

c++ - `thread_local` 全局变量什么时候初始化?

java - 当 Hibernate 已经存在并使用缓存时使用 memcached

java - MaterialAnimatedSwitch 保存状态

java - 如何使用 hibernate 获取所选表的最后一列?

c++ - std::notify_all_at_thread_exit 是如何工作的?

Java 在 SwingWorker 中渲染游戏 map 无法正常工作

c# - 为什么我可以锁定 C# 中的任何对象类型?