java 关键字 volatile 不起作用

问题描述 投票:0回答:1

我做了一个实验来验证 volatile 的可见性

这是我的测试代码

package org.example;

// 按两次 Shift 打开“随处搜索”对话框并输入 `show whitespaces`,
// 然后按 Enter 键。现在,您可以在代码中看到空格字符。
public class Main3 {

    private static volatile int i = 0;

    private static Object o = new Object();

    private static void add()
    {
        i++;
    }

    public static void main(String[] args) throws InterruptedException {

        //The first thread performs the write operation
        Thread th1 = new Thread( () -> {
            for (int j = 0; j < 10000; ) {


                synchronized (o)
                {
                    add();

                    o.notify();

                }


            }
        } );

        //The second thread performs the read operation
        Thread th2 = new Thread( () -> {
            for (int j = 0; j < 10000; ) {

                int before = i;

                synchronized (o)
                {
                    try {
                        o.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                int after = i;

                if( before == after )
                {
                    System.out.println( "before: " + before + ", after: " + after );
                }

            }
        } );


        th1.start();
        th2.start();

        th1.join();
        th2.join();

        System.out.println(i);


    }
}

这是我的测试结果

before: 1201480699, after: 1201480699
before: 1220677165, after: 1220677165
before: 1791815757, after: 1791815757
before: 1807949964, after: 1807949964
before: 1940818428, after: 1940818428
before: -1687188478, after: -1687188478
before: -1462842855, after: -1462842855

我想展示第一个线程执行i++,第二个线程看i的变化, 第一个线程和第二个线程通过等待和通知机制交替执行, 但它实际上并没有按照我想要的方式进行。

我想知道为什么关键字 volatile 不起作用

java volatile
1个回答
0
投票

我在您的代码中看到了一些问题。首先,这两个循环都是无限的,因为它们不递增变量

j
。接下来,使用
notify/wait
wait
方法的javadoc (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait)特别提到“中断和虚假唤醒是可能的,并且此方法应始终在循环中使用”。
wait
方法设计用于条件变量(https://cseweb.ucsd.edu/classes/sp17/cse120-a/applications/ln/lecture7.html),因此需要同时存在一个条件和一个循环。另外,还有一个逻辑错误。如果目标是对写入和读取进行排序,以便前后永远不相等,则需要在调用
wait
之前进行
add
调用,并在执行前读取之后进行
notify
调用。下面,我列出了我认为您正在尝试做的事情的工作版本。

public class Main3 {
    private static volatile int i = 0;
    private static volatile boolean write = false;
    private static volatile boolean read = false;
    private static final Object o = new Object();

    private static void add()
    {
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        //The first thread performs the write operation
        Thread th1 = new Thread( () -> {
            for (int j = 0; j < 10000; j++) {
                synchronized (o)
                {
                    while (!write) {
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    add();
                    write = false;
                    read = true;
                    o.notify();
                }
            }
        } );

        //The second thread performs the read operation
        Thread th2 = new Thread( () -> {
            for (int j = 0; j < 10000; j++) {
                int before;
                int after;
                synchronized (o)
                {
                    before = i;
                    
                    write = true;
                    o.notify();
                    
                    while (!read) {
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    read = false;
                    after = i;
                }
                
                if( before == after )
                {
                    System.out.println( "before: " + before + ", after: " + after );
                }
            }
        } );


        th1.start();
        th2.start();

        th1.join();
        th2.join();

        System.out.println(i);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.