当前位置: 首页 > 知识库问答 >
问题:

为什么即使我修改了锁变量,也会得到一个无限的while循环?[重复]

鲍永春
2023-03-14
public class GuardedBlock {

    private boolean guard = false;

    private static void threadMessage(String message) {
        System.out.println(Thread.currentThread().getName() + ": " + message);
    }

    public static void main(String[] args) {
        GuardedBlock guardedBlock = new GuardedBlock();

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    guardedBlock.guard = true;
                    threadMessage("Set guard=true");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                threadMessage("Start waiting");
                while (!guardedBlock.guard) {
                    //threadMessage("Still waiting...");
                }
                threadMessage("Finally!");
            }
        });

        thread1.start();
        thread2.start();
    }
}

我是通过java基础教程学习并发性的。到了警戒区,试着测试一下。有一件事我无法理解。

While循环是无限的,但是如果取消对threadMessage line的注释,一切都会正常工作。为什么?

共有3个答案

萧麒
2023-03-14

while循环是无限的原因是条件!guardedBlock.guard始终为true。这意味着guardedBlock.guard=true;在线程1中设置的没有为线程2设置,这是因为您没有将变量保护用作易失性

让我从维基百科本身复制在java中使用易失性的必要性:

在所有版本的Java中,对volatile变量的读取和写入都有全局顺序。这意味着每个访问volatile字段的线程在继续之前都会读取其当前值,而不是(可能)使用缓存的值。

希望有所帮助。

解宏扬
2023-03-14

Jean Francois的解决方案是正确的:当线程访问共享变量时,绝对必须进行某种同步,无论是通过volatilesynchronized.等等。

我还要补充一点,您的while循环相当于所谓的忙等待,即在并发设置中重复测试条件。此代码中繁忙等待的紧密循环可能会占用CPU。至少它对系统资源的影响是不可预测的。

您可能希望探索处理受单个共享条件影响的多个线程的条件变量方法。Java在Java.util中有许多更高级的工具。concurrent库,但最好了解较旧的较低级别API方法,特别是因为您已经直接使用了Threadinstances。

每个Object都有wait()notifyAll()方法。对象表示条件,或至少表示与之关联的监视器。wait()方法在测试条件的whileloop中调用,并阻塞调用线程,直到其他线程调用 。然后所有等待的线程都将被唤醒,它们都将争夺锁和再次测试条件的机会。如果条件在那一点上保持为真,那么所有线程都将继续。

以下是使用这种方法的代码外观:

public class GuardedBlock {

    private boolean guard = false;

    private static void threadMessage(String message) {
        System.out.println(Thread.currentThread().getName() + ": " + message);
    }

    public static void main(String[] args) throws Exception {
        GuardedBlock guardedBlock = new GuardedBlock();

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    synchronized (guardedBlock) {
                        guardedBlock.guard = true;
                        guardedBlock.notifyAll();
                    }
                    threadMessage("Set guard=true");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                threadMessage("Start waiting");
                while (!guardedBlock.guard) {
                    synchronized (guardedBlock) {
                        try {
                            guardedBlock.wait();
                        }
                        catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                threadMessage("Finally!");
            }
        });

        thread1.start();
        thread2.start();
        thread2.join();

        System.out.println("Done");
    }
}

请注意以下几点:

  • 无论何时读取或写入条件,都必须锁定(通过synchronized关键字)
  • guard条件在whileloop中测试,但该循环在 wait()而循环的唯一原因是处理存在许多线程且条件多次更改的情况。然后,当一个线程被唤醒时,应该重新测试该条件,以防另一个线程在唤醒和重新获取锁之间的微小间隙中改变该条件
  • guard条件设置为true时,等待线程会得到通知(通过 calls)
  • 最后,thread2实例上的程序块,这样我们就不会在所有线程完成之前退出主线程(通过join()call)

如果您查看ObjectAPI,您会看到还有一个通知()方法。始终使用notefyAll()更简单,但如果您想了解这两种方法之间的区别,请参阅这篇SO帖子。

司徒高丽
2023-03-14

简短的回答

您忘记将 guard 声明为易失性布尔值。

如果您省略字段的声明为易失性,则不会告诉JVM此字段可以被多个线程看到,在您的示例中就是这种情况。

在这种情况下,< code>guard的值将只被读取一次,并将导致无限循环。它将被优化成这样(没有打印) :

if(!guard)
{
    while(true)
    {
    }
}

那么,为什么< code>System.out.println要改变这种行为呢?因为< code >写入是同步的,这迫使线程不缓存读取。

这里是System.out使用的PrintStream方法之一的代码粘贴。println

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

write方法:

private void write(String s) {
    try {
        synchronized (this) {
            ensureOpen();
            textOut.write(s);
            textOut.flushBuffer();
            charOut.flushBuffer();
            if (autoFlush && (s.indexOf('\n') >= 0))
                out.flush();
        }
    }
    catch (InterruptedIOException x) {
        Thread.currentThread().interrupt();
    }
    catch (IOException x) {
        trouble = true;
    }
}

请注意同步。

 类似资料:
  • 问题内容: public class GuardedBlock { 我正在通过Java Essentials教程学习并发性。找到了守卫的方块并尝试对其进行测试。我无法理解的一件事。 虽然循环是无限的,但是如果您取消注释threadMessage行,则一切正常。为什么? 问题答案: 简短答案 您忘了声明为易失性布尔值。 如果您将字段声明省略为,则不会告诉JVM该字段可以被多线程看到,例如您的示例。

  • 我是Java新手。我正在尝试遍历一个文件并将每个字符串写入ArrayList。为此,我有如下代码: 然而,由于某种原因,这段代码陷入了无限循环。即使我将打印语句作为当块的第一行,它也不会执行。 accountData.txt文件是这样排列的: 我已经读了很多遍,经常读到这是个问题,因为人们没有用Scanner.next()来移动指针,但我相信我正在这样做。我不知道哪里出了问题。 感谢您的帮助!

  • 我有一个看起来很简单的问题,但由于某种原因我无法绕过它。基本上我的程序正在导致一个无限循环,我不知道为什么。 下面是我陷入的特定循环: 当我运行它时,它总是问我输入列#。我给它一个数字,它接受这个数字,$response变为True,但while循环继续运行,就好像<code>的$response</code>为false一样。我是Perl新手,所以可能我遗漏了一些东西,但是($response=

  • 我的循环一直在进行,即使我输入了y、yes、n或no。 输出: 抱歉,该答案无效,请重试。是否要重新滚动模具1?不 抱歉,该答案无效,请重试。是否要重新滚动模具1?对 抱歉,该答案无效,请重试。是否要重新滚动模具1?对

  • 我正在构建一个gradebook来存储学生和教师,每个学生和教师都有一个唯一的ID,以及他们各自在Student和Teacher对象的ArrayList中注册或教学的类。我有文件夹路径“j:/compsci/类/”,为每个类存储一个文本文件。 文本文件格式: 第1行:班级名称、教师ID、期间、荣誉?、班级ID 第2行:班级中每个学生的学生ID(用逗号分隔)。 在这里,我初始化了每个学生正在接受的所

  • 在下面的程序中,如果我在第一条if语句中将变量$foo设置为值1,那么它的工作原理是在if语句之后记住它的值。然而,当我将同一变量设置为while语句中的if语句中的值2时,它会在while循环后被遗忘。它的行为就像我在循环中使用变量$foo的某种副本,而我只修改特定的副本。下面是一个完整的测试程序: