使用join()方法等待线程执行完成,但未获得期望的结果。

huangapple go评论62阅读模式
英文:

Using join() method to wait for the thread execution to complete but not getting the desired result

问题

// 创建了2个线程并调用了它们的 `join()` 方法。我期望这些线程会按顺序运行并给出期望的结果,但实际上我得到了一些随机的值。

public class TestJoinMethod {
    volatile private int count =0;

    public static void main(String[] args) {
        TestJoinMethod testJoinMethod = new TestJoinMethod();
        testJoinMethod.execute();
    }

    public void execute() {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0; i<10000; i++) {
                    count++;
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0; i<10000; i++) {
                    count++;
                }
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            System.out.println(count);
            t2.join();
            System.out.println(count);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

使用上述代码来获取 count 变量的值。根据我的理解,join() 方法会等待线程执行完成,然后才将控制权交给调用线程的下一个语句。期望结果:sysout 语句应分别打印 10000、200000。然而,结果却是打印了一些随机的值:14125,14125 或 16911,16911。不明白为什么会发生这种情况,已经查阅了一些教程,但无法理解确切的问题所在。

感谢您的帮助。

英文:

Created 2 threads and called the join() method on them. I was expecting the threads would run sequentially and give the desired result, but I am getting some random values in the result.

public class TestJoinMethod {
    volatile private int count =0;

    public static void main(String[] args) {
        TestJoinMethod testJoinMethod = new TestJoinMethod();
        testJoinMethod.execute();
    }

    public void execute() {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0; i<10000; i++) {
                    count++;
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0; i<10000; i++) {
                    count++;
                }
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            System.out.println(count);
            t2.join();
            System.out.println(count);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

Using the above code to get the value of count variable. As per my understanding the join() method waits for the thread execution to be completed before the control of the calling thread goes to next statement. Expected result : sysout statement should print 10000 , 200000 respectively. However, the result is giving some random values : 14125 , 14125 or 16911, 16911.
Not able to understand why is this happening here, went through some tutorials also, but couldn't understand the exact issue here.

Appreciate your help here.

答案1

得分: 2

下面是翻译好的部分:

"t1.start();
t2.start();"

启动了两个线程,它们同时运行。我们还没有执行join(),只是启动了这两个线程,所以它们都在运行。

"try {
t1.join();
System.out.println(count);"

然后我们执行join()。在这一点上,t1t2都已经运行了一段时间。最终,t1完成,然后我们打印出count。但是t2也一直在运行,所以我们预期count的增加量会比仅由t1增加的要多。

然后我们来到这里:

"t2.join();
System.out.println(count);"

这几乎做了相同的事情:等待t2完成,然后再次打印出count

然而,几乎不可能预测t1t2的执行会有多大的重叠。也许有很多重叠,但也可能几乎没有重叠。

更糟糕的是,您没有采取任何措施来保护对count的访问,这会导致数据竞争。结果可以用一个词来形容,那就是垃圾。

要获得您显然希望得到的结果,您需要防止线程并发访问(换句话说,使线程的使用完全没有意义)。显而易见的方法是这样的:

t1.start();
t1.join();
System.out.println(count);
t2.start();
t2.join();
System.out.println(count);

这应该能产生您期望的结果,但它等同于在一个线程中运行所有代码,所以这一切都没有什么实际意义。

英文:

So let's think about the sequence that happens here. This part:

    t1.start();
    t2.start();

Starts the two threads running, concurrently with each other. We haven't executed a join() yet, just started the two threads, so they're both running.

    try {
        t1.join();
        System.out.println(count);

Then we get to this join(). Both t1 and t2 have been running for a while at this point. Finally, t1 finishes and then we print out count. But t2 has also been running, so we expect count to have been incremented more than it would have been by t1 alone.

Then we get to this:

        t2.join();
        System.out.println(count);

This does pretty much the same thing: wait for t2 to finish, then print out count again.

It's essentially impossible to predict how much of the execution of t1 and t2 may overlap with each other though. Maybe a lot, but maybe almost none at all.

Worse, you haven't done anything to protect access to count, you're getting a data race. The result is, in a word, garbage.

To get the result you're apparently looking for, you'd have prevent concurrent access of your threads though (in other words, render the use of threads utterly useless). The obvious way to do that would be something like:

t1.start();
t1.join();
System.out.println(count);
t2.start();
t2.join();
System.out.println(count);

This should produce the result you desire--but it's equivalent to just running all the code in one thread, so there's no real point to any of it.

答案2

得分: 1

编译器已经在警告你

> 对 volatile 字段 'count' 的非原子操作

线程可以随机读取相同的值并同时更新它,这将导致相同的数字。例如:

  1. 线程 1 读取 count = 0
  2. 线程 2 读取 count = 0
  3. 线程 1 执行 0+1
  4. 线程 1 更新 count = 1
  5. 线程 2 执行 0+1
  6. 线程 2 更新 count = 1

可以通过同步访问 count 变量来解决这个问题,例如使用 AtomicInteger

private final AtomicInteger count = new AtomicInteger(0);

count.getAndIncrement();

另请注意,最终结果对于两个线程都将是 20000,因为你在递增相同的计数器。

英文:

The compiler already is warning you that

> Non-atomic operation on volatile field 'count'

The threads can randomly read the same value and update it at the same time, which will result in the same number. For example:

  1. Thread 1 reads count = 0
  2. Thread 2 reads count = 0
  3. Thread 1 does 0+1
  4. Thread 1 updates count = 1
  5. Thread 2 does 0+1
  6. Thread 2 updates count = 1

This can be solved synchronizing the access to the count variable, for example using AtomicInteger:

private final AtomicInteger count = new AtomicInteger(0);

count.getAndIncrement();

Also note that the final result will be 20000 for both threads, you're incrementing the same counter.

huangapple
  • 本文由 发表于 2023年2月6日 17:00:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75359206.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定