简体   繁体   English

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

[英]Object Sharing in Simple Multi-threaded Program

Introduction 介绍

I have written a very simple program as an attempt to re-introduce myself to multi-threaded programming in JAVA. 我写了一个非常简单的程序,试图将自己重新引入JAVA中的多线程编程。 The objective of my program is derived from this rather neat set of articles, written by Jakob Jankov. 我的计划的目标是来源于 ,而整洁的系列文章,由雅各布Jankov写的。 For the program's original, unmodified version, consult the bottom of the linked article. 有关程序的原始未修改版本,请参阅链接文章的底部。

Jankov's program does not System.out.println the variables, so you cannot see what is happening. Jankov的程序没有System.out.println变量,因此您看不到正在发生什么。 If you .print the resulting value you get the same results, every time (the program is thread safe); 如果您.print结果值,则每次都会得到相同的结果(程序是线程安全的); however, if you print some of the inner workings, the "inner behaviour" is different, each time. 但是,如果您打印一些内部工作原理,则每次的“内部行为”都是不同的。

I understand the issues involved in thread scheduling and the unpredictability of a thread's Running . 我了解线程调度中涉及的问题以及线程的Running的不可预测性。 I believe that may be a factor in the question I ask, below. 我认为这可能是我下面提出的问题的一个因素。

Program's Three Parts 程序的三个部分

The Main Class: 主类:

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

The objective of the above class is simply to share an object. 上一类的目的仅仅是共享一个对象。 In this case, the threads share an object of type Counter : 在这种情况下,线程共享一个Counter类型的对象:

Counter Class 柜台类

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

The above is simply the definition of the Counter class, which includes only a primitive member of type long . 上面仅是Counter类的定义,该类仅包含long类型的基本成员。

CounterThread Class CounterThread类

Below, is the CounterThread class, virtually unmodified from the code provided by Jankov. 下面是CounterThread类,它实际上未从Jankov提供的代码中进行修改。 The only real difference (despite my implementing Runnable as opposed to extending Thread ) is the addition of System.out.println() . 唯一真正的区别(尽管我实现了 Runnable而不是扩展 Thread )是System.out.println() I added this to watch the inner-workings of the program. 我添加了它以观察程序的内部工作原理。

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

Question

As you can see, the code is very simple. 如您所见,代码非常简单。 The above code's only purpose is to watch what happens as two threads share a thread-safe object. 上面的代码的唯一目的是观察两个线程共享一个线程安全对象时发生的情况。

My question comes as a result of the output of the program (which I have tried to condense, below). 我的问题来自程序输出的结果(我在下面尝试进行了总结)。 The output is hard to "get consistent" to demonstrate my question, as the spread of the difference (see below) can be quite great: 输出很难“保持一致”以证明我的问题,因为差异的扩散(请参见下文)可能非常大:

Here's the condensed output (trying to minimize what you look at): 这是压缩的输出(试图将您的外观最小化):

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

And one more output that better demonstrates: 还有一个更好地演示的输出:

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

My question(s): 我的问题:

Thread safety ensures the safe mutability of a variable, ie only one thread can access an object at a time. 线程安全性确保变量的安全可变性,即一次只能有一个线程可以访问一个对象。 Doing this ensures that the "read" and "write" methods will behave, appropriately, only writing after a thread has released its lock (eliminating racing). 这样做可以确保“读取”和“写入”方法仅在线程释放其锁定(消除竞速)后才能正确执行操作。

Why, despite the correct write behaviour, does counterThread2 "believe" Counter 's value ( not the iterator i ) to still be zero? 为什么尽管写行为正确, counterThread2 “相信” Counter的值( 而不是迭代器 i )仍为零? What is happening in memory? 内存中正在发生什么? Is this a matter of the thread containing it's own, local Counter object? 这是否是包含它自己的本地Counter对象的线程的问题?

Or, more simply, after counterThread1 has updated the value, why does counterThread2 not see - in this case, System.out.println() - the correct value? 或者,更简单地说,在counterThread1更新值之后,为什么counterThread2看不到正确的值System.out.println()在本例中为System.out.println() Despite not seeing the value , the correct value is written to the object. 尽管看不到该值 ,但正确的值已写入对象。

Why, despite the correct write behaviour, does counterThread2 "believe" Counter's value to still be zero? 为什么尽管写行为正确,counterThread2是否仍“相信” Counter的值仍为零?

The threads interleaved in such a way to cause this behaviour. 线程以导致这种行为的方式交错。 Because the print statements are outside of the synchronised block, it is possible for a thread to read the counter value then pause due to is scheduling while the other thread increments multiple times. 由于打印语句位于同步块之外,因此线程可能会读取计数器值,然后由于调度而暂停,而另一个线程会多次递增。 When the waiting thread finally resumes and enters the inc counter method, the value of the counter will have moved on quite a bit and will no longer match what was printed in the BEFORE log line. 当等待线程最终恢复运行并进入inc计数器方法时,计数器的值将继续移动很多,不再与BEFORE日志行中打印的内容匹配。

As an example, I have modified your code to make it more evident that both threads are working on the same counter. 例如,我修改了您的代码,以使两个线程都在同一计数器上工作更加明显。 First I have moved the print statements into the counter, then I added a unique thread label so that we can tell which thread was responsible for the increment and finally I only increment by one so that any jumps in the counter value will stand out more clearly. 首先,我将打印语句移到了计数器中,然后添加了一个唯一的线程标签,以便我们可以知道哪个线程负责增量,最后我只增加了一个,以便计数器值的任何跳跃都将更加明显。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM