多线程如何与 Java 中的循环一起工作?

当我使用 2 个线程调用同一对象 (new Counter()) 的方法时,while 循环运行的次数超过了循环限制。有人可以用简单的语言解释相同的原因。


主要课程:


public class MainClass {


public static void main(String[] args) {


    Counter c1 = new Counter();


    MyRunnable r1 = new MyRunnable(c1);


    Thread t1 = new Thread(r1, "t1");

    Thread t2 = new Thread(r1, "t2");

    t1.start();

    t2.start();


}


}

可运行:


public class MyRunnable implements Runnable {


Counter counter;


public MyRunnable(Counter counter) {

    this.counter = counter;

}


@Override

public void run() {

    counter.add();

}


}

柜台类:


public class Counter {

    int i = 1;


    public void add() {


        while (i <= 5) {

            System.out.println(i + " " + Thread.currentThread().getName());

            i++;

        }


    }


}

输出 :


1 t1

1 t2

2 t1

3 t2

4 t1

5 t2

我想知道,如果限制是 5,那为什么输出 6 个值。有时它也输出5个值?如果有人对此有所了解,那就太好了。谢谢。


叮当猫咪
浏览 163回答 4
4回答

浮云间

您的代码存在两个并发问题。对于并发任务,共享 inti既没有适当声明也没有访问。它至少需要是易失性的,但为了正确处理原子增量应该是一个AtomicInteger.&nbsp;Volatile 将有助于保证其他线程可以看到值是否发生变化(JVM 无法保证这一点),但原子整数更进一步,它提供了原子操作,这才是您真正需要的。您的循环可以同时被两个线程访问,因为条件检查与变量的增量是分开的。要正确处理运行循环的并发线程,您需要while此处的循环条件也将增量作为单个原子操作执行。或者你需要添加一个同步块。也许尝试类似下面未经测试的代码:public class Counter {&nbsp; &nbsp; private final AtomicInteger i = new AtomicInteger(1);&nbsp; &nbsp; public void add() {&nbsp; &nbsp; &nbsp; &nbsp; for (;;)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int localCount = i.getAndIncrement();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (localCount > 5) { break; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(localCount + " " + Thread.currentThread().getName());&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}逐行解释一下上面的代码:我们切换i到 an&nbsp;AtomicInteger,因为它允许我们获取当前值并自动将下一个值加一。这种原子性很重要,因为否则两个竞争线程可能会增加到相同的值,而不是在所有线程中读取和增加每个值恰好一次。我们可以通过将代码的递增部分放入同步块中来实现相同的目的。我们切换到一个for (;;)循环,以便我们可以将循环控制/终止代码移动到循环体内。这使我们能够轻松地将原子增量的结果用于控制代码和循环体内(例如在语句中println)。i.getAndIncrement()原子地增加我们的计数器并返回给我们增加之前的值。无论有多少线程在运行,都可以保证此操作有效,并确保我们不会遗漏或重复任何数字。将先前的计数值分配给localCountint 为我们提供了在循环中使用的值。如果我们尝试i在循环内重新访问而不是使用 localCount,那么我们最终会在某个时刻得到不同的数字,因为其他线程正在同时递增i。我们检查localCount它是否超出了我们的终止限制,如果超过了,我们就中断循环。我们不检查,i因为它可能已被另一个线程修改,并且我们只关心该线程当前的数量是否超出限制。最后,我们使用localCountinstead of 来执行 println&nbsp;i,因为i在代码到达此处时可能已被另一个线程修改(这将导致打印重复行)。请注意,此代码不保证计数器的数字将按顺序打印。&nbsp;很可能大多数时候都是这种情况,但肯定不能保证也不应该依赖它。您的问题没有说明实际的预期/要求的结果,如果这是一项要求,那么您很可能需要围绕增量和 println 语句进行同步,以确保不会乱序执行。例如同步解决方案:public class Counter {&nbsp; &nbsp; int i = 1;&nbsp; &nbsp; public void add() {&nbsp; &nbsp; &nbsp; &nbsp; for (;;) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; synchronized(this) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (i > 5) { break; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(i + " " + Thread.currentThread().getName());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i++;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}

慕标5832272

这是个简单的。您创建了一个名为c1;的计数器您从此计数器创建了一个 Runnable;您将同一个可运行对象传递给了两个线程。两个线程现在将在同一个 Counter 对象上运行c1;您在方法内运行 while 循环add()。该add()方法在一行上打印一些内容,并在下一行上递增i。对于您当前的输出,在第一个线程执行其 print 语句后,它被中断,第二个线程获得了 CPU 的控制权。第二个线程设法执行了它的打印。然后任一线程递增计数器(我们不知道是哪一个),并且打印和计数器递增两个步骤继续。

BIG阳

问题是您正在i同时访问该变量。Java 将变量缓存在 CPU 缓存中。由于每个线程都可以在不同的内核中执行,因此每个线程中的值i可能不同。即使您正在执行i++底层代码也是i = i + 1.&nbsp;您正在读取变量,然后正在写入,因此在这种情况下多个线程也可能具有不同的值。这个问题的解决方案是像你的情况一样使用Atomic变量。AtomicInteger这些原子变量是线程安全的,并且像在事务中一样读写,在更新变量时阻塞对线程的访问。在您的情况下,int您可以声明而不是声明&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;volatile&nbsp;AtomicInteger&nbsp;atomicInteger&nbsp;=&nbsp;new&nbsp;AtomicInteger(1);而不是使用i++你可以使用atomicInteger.addAndGet(1)

当年话下

您刚刚发现的是竞争条件线程可以counter不受任何限制地访问对象。即使i是 int,也不能保证,例如,当“t2”读取该值时,“t1”完成了该值的递增。尝试运行该程序几次,输出可能会有所不同。要解决你的问题,你需要实现一种锁,或者使用关键字synchronized。一般来说,在实现使用多线程的应用程序时,您需要识别关键部分并确保避免竞争条件。编辑:我不敢相信我写的protected不是synchronized,不知道这是怎么发生的。就在今天偶然发现了这个,编辑所以没有其他人被误导。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java