即使我修改了锁变量,为什么我会得到一个无限循环?

时间:2015-06-21 13:47:27

标签: java multithreading

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 essentials教程学习并发。得到防护块并试图测试它。有一点我无法理解。

虽然循环是无限的,但如果取消注释threadMessage行,一切正常。为什么呢?

3 个答案:

答案 0 :(得分:15)

简短回答

您忘记将guard声明为易变的布尔值。

如果你将字段的声明省略为volatile,那么你并没有告诉JVM多个线程可以看到这个字段,例如你的情况。

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

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

为什么System.out.println会改变这种行为?因为writes是同步的,它会强制线程不缓存读取。

此处粘贴了println使用的PrintStream System.out.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;
    }
}

注意同步。

答案 1 :(得分:3)

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

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

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

每个Object都有wait()notifyAll()种方法。 Object代表条件,或者至少代表与之关联的监视器。在wait()循环中调用while方法来测试条件,并阻塞调用线程,直到某个其他线程调用notifyAll()。然后所有等待的线程都会被唤醒,他们都将争夺锁定并再次测试这个条件的机会。如果条件在该点保持为真,则所有线程都将继续。

以下是使用此方法的代码:

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条件在while循环内进行测试,但该循环在wait()调用期间阻塞。它仍然是while循环的唯一原因是处理有许多线程且条件发生多次变化的情况。然后,当线程被唤醒时,应该重新测试该条件,以防另一个线程在唤醒和重新获取锁之间的微小间隙中改变条件。
  • guard条件设置为true时(通过notifyAll()来电,会通知等待线程。)
  • thread2实例上的程序块 非常结束,这样我们就不会在所有线程之前退出主线程 完成(通过join()电话。)

如果查看Object API,您会看到还有notify()方法。始终使用notifyAll()更简单,但如果您想了解这两种方法之间的区别,see this SO post

答案 2 :(得分:1)

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

让我复制从维基百科本身使用java中的volatile的需要:

  

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

希望有所帮助。