我做了一个实验来验证 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 不起作用
我在您的代码中看到了一些问题。首先,这两个循环都是无限的,因为它们不递增变量
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);
}
}