为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

叮当猫咪 2023-06-21 13:59:34
当我使用 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;}@Overridepublic 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 t11 t22 t13 t24 t15 t2我想知道,如果限制是 5,那为什么输出 6 个值。有时它也输出5个值?如果有人对此有所了解,那就太好了。谢谢。
查看完整描述

4 回答

?
浮云间

TA贡献1829条经验 获得超4个赞

您的代码存在两个并发问题。

  1. 对于并发任务,共享 inti既没有适当声明也没有访问。它至少需要是易失性的,但为了正确处理原子增量应该是一个AtomicInteger. Volatile 将有助于保证其他线程可以看到值是否发生变化(JVM 无法保证这一点),但原子整数更进一步,它提供了原子操作,这才是您真正需要的。

  2. 您的循环可以同时被两个线程访问,因为条件检查与变量的增量是分开的。要正确处理运行循环的并发线程,您需要while此处的循环条件也将增量作为单个原子操作执行。或者你需要添加一个同步块。

也许尝试类似下面未经测试的代码:

public class Counter {

    private final AtomicInteger i = new AtomicInteger(1);


    public void add() {

        for (;;)

        {

            int localCount = i.getAndIncrement();

            if (localCount > 5) { break; }


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

        }

    }

}

逐行解释一下上面的代码:

  • 我们切换i到 an AtomicInteger,因为它允许我们获取当前值并自动将下一个值加一。这种原子性很重要,因为否则两个竞争线程可能会增加到相同的值,而不是在所有线程中读取和增加每个值恰好一次。我们可以通过将代码的递增部分放入同步块中来实现相同的目的。

  • 我们切换到一个for (;;)循环,以便我们可以将循环控制/终止代码移动到循环体内。这使我们能够轻松地将原子增量的结果用于控制代码和循环体内(例如在语句中println)。

  • i.getAndIncrement()原子地增加我们的计数器并返回给我们增加之前的值。无论有多少线程在运行,都可以保证此操作有效,并确保我们不会遗漏或重复任何数字。将先前的计数值分配给localCountint 为我们提供了在循环中使用的值。如果我们尝试i在循环内重新访问而不是使用 localCount,那么我们最终会在某个时刻得到不同的数字,因为其他线程正在同时递增i

  • 我们检查localCount它是否超出了我们的终止限制,如果超过了,我们就中断循环。我们不检查,i因为它可能已被另一个线程修改,并且我们只关心该线程当前的数量是否超出限制。

  • 最后,我们使用localCountinstead of 来执行 println i,因为i在代码到达此处时可能已被另一个线程修改(这将导致打印重复行)。

请注意,此代码不保证计数器的数字将按顺序打印。 很可能大多数时候都是这种情况,但肯定不能保证也不应该依赖它。您的问题没有说明实际的预期/要求的结果,如果这是一项要求,那么您很可能需要围绕增量和 println 语句进行同步,以确保不会乱序执行。

例如同步解决方案:

public class Counter {

    int i = 1;


    public void add() {

        for (;;) {

            synchronized(this) {

                if (i > 5) { break; }

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

                i++;

            }

        }

    }

}


查看完整回答
反对 回复 2023-06-21
?
慕标5832272

TA贡献1966条经验 获得超4个赞

这是个简单的。

  1. 您创建了一个名为c1;的计数器

  2. 您从此计数器创建了一个 Runnable;

  3. 您将同一个可运行对象传递给了两个线程。两个线程现在将在同一个 Counter 对象上运行c1

  4. 您在方法内运行 while 循环add()

add()方法在一行上打印一些内容,并在下一行上递增i


对于您当前的输出,在第一个线程执行其 print 语句后,它被中断,第二个线程获得了 CPU 的控制权。第二个线程设法执行了它的打印。然后任一线程递增计数器(我们不知道是哪一个),并且打印和计数器递增两个步骤继续。


查看完整回答
反对 回复 2023-06-21
?
BIG阳

TA贡献1859条经验 获得超6个赞

问题是您正在i同时访问该变量。Java 将变量缓存在 CPU 缓存中。由于每个线程都可以在不同的内核中执行,因此每个线程中的值i可能不同。即使您正在执行i++底层代码也是i = i + 1. 您正在读取变量,然后正在写入,因此在这种情况下多个线程也可能具有不同的值。

这个问题的解决方案是像你的情况一样使用Atomic变量。AtomicInteger这些原子变量是线程安全的,并且像在事务中一样读写,在更新变量时阻塞对线程的访问。

在您的情况下,int您可以声明而不是声明

    private volatile AtomicInteger atomicInteger = new AtomicInteger(1);

而不是使用i++你可以使用atomicInteger.addAndGet(1)


查看完整回答
反对 回复 2023-06-21
?
当年话下

TA贡献1890条经验 获得超9个赞

您刚刚发现的是竞争条件线程可以counter不受任何限制地访问对象。即使i是 int,也不能保证,例如,当“t2”读取该值时,“t1”完成了该值的递增。尝试运行该程序几次,输出可能会有所不同。

要解决你的问题,你需要实现一种锁,或者使用关键字synchronized。一般来说,在实现使用多线程的应用程序时,您需要识别关键部分并确保避免竞争条件。

编辑:我不敢相信我写的protected不是synchronized,不知道这是怎么发生的。就在今天偶然发现了这个,编辑所以没有其他人被误导。


查看完整回答
反对 回复 2023-06-21
  • 4 回答
  • 0 关注
  • 170 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信