我正在尝试在多线程中学习volatile
field modifier。我发现了这句话:
当一个线程读取和写入共享变量而其他线程读取相同时,首选易失性。然而,如果共享变量上有两个以上的线程执行读写操作,那么只有volatile是不够的,你也需要同步。
我知道volatile
提供了可见性和发生之前的保证,但是是否可以提供一个简单的小代码示例来演示上面需要同步块的语句?
答案 0 :(得分:3)
public class TwoInts {
private volatile int i1;
private volatile int i2;
public void set(int i1, int i2) {
this.i1 = i1;
this.i2 = i2;
}
public boolean same() {
return i1 == i2;
}
}
现在,如果你有一个线程这样做:
while (true) {
twoInts.set(i, i);
i++;
}
和第二个线程这样做:
while (true) {
if (!twoInts.same()) {
System.out.println("Ooops!!");
}
}
然后您将观察引用文本正在讨论的问题。如果你重写TwoInts
类来制作方法synchronized
那么“Oooops !!”消息将停止。
答案 1 :(得分:2)
有很多这样的例子......这里有一个:
volatile int i = 0;
// Thread #1
while (true) {
i = i + 1;
}
// Thread #2
while (true) {
Console.WriteLine(i);
}
在这种情况下,线程#1和线程#2都在读取变量i
,但只有线程#1正在写入它。线程#2将始终看到i
的递增值。
如果没有volatile关键字,您偶尔会看到奇怪的行为,通常是在多处理器计算机或多核CPU上。发生了什么(这里稍微简化)是线程#1和#2都在自己的CPU上运行,每个都获得它自己的i
副本(在它的CPU缓存中和/或寄存器)。如果没有volatile关键字,他们可能永远不会互相更新已更改的值。
与此示例形成对比:
static volatile int i = 0;
// Thread #1
while (true) {
i = i + 1;
}
// Thread #2
while (true) {
if (i % 2 == 0)
i == 0;
else
Console.WriteLine(i);
}
所以在这里,线程#1试图单调递增i
,而线程#2要么将i
设置为0(如果i
是偶数)或将其打印到控制台,如果我是奇怪的。你会期望Thread#2永远不会在控制台上输出偶数,对吗?
i
的访问没有同步,所以Thread#2可能看到一个奇数值,移动到else
分支,然后Thread#1递增i
的值。 },导致线程#2打印出偶数。
在这种情况下,解决问题的一种方法是使用基本锁定作为同步的一种形式。因为我们无法锁定基元,所以我们引入一个空白对象来锁定:
static volatile int i = 0;
static Object lockOnMe = new Object();
// Thread #1
while (true) {
lock (lockOnMe) {
i = i + 1;
}
}
// Thread #2
while (true) {
lock (lockOnMe) {
if (i % 2 == 0)
i == 0;
else
Console.WriteLine(i);
}
}
答案 2 :(得分:2)
假设您有int i
和两个帖子,您希望每个人都阅读i
并设置i = i + 1
。
像这样:
public class Main {
private static volatile int i = 0;
public static void main(String[] args) throws Exception{
Runnable first = new Runnable() {
@Override
public void run() {
System.out.println("Thread_1 see i = " + i);
i++;
System.out.println("Thread_1 set i = " + i);
}
};
Runnable second = new Runnable() {
@Override
public void run() {
System.out.println("Thread_2 see i = " + i);
i++;
System.out.println("Thread_2 set i = " + i);
}
};
new Thread(first).start();
new Thread(second).start();
}
}
结果是:
Thread_1 see i = 0
Thread_2 see i = 0
Thread_1 set i = 1
Thread_2 set i = 2
如您所见,Thread_2获得0
并设置2
(因为Thread_1已将i
更新为1
),这是不期望的。
添加同步后,
public class Main {
private static volatile int i = 0;
public static void main(String[] args) throws Exception{
Runnable first = new Runnable() {
@Override
public void run() {
synchronized (Main.class) {
System.out.println("Thread_1 see i = " + i);
i++;
System.out.println("Thread_1 set i = " + i);
}
}
};
Runnable second = new Runnable() {
@Override
public void run() {
synchronized (Main.class) {
System.out.println("Thread_2 see i = " + i);
i++;
System.out.println("Thread_2 set i = " + i);
}
}
};
new Thread(first).start();
new Thread(second).start();
}
}
有效:
Thread_2 see i = 0
Thread_2 set i = 1
Thread_1 see i = 1
Thread_1 set i = 2