Java 和 when/how 中的 Volatile 是什么,我们应该使用它们吗?
What is Volatile in Java and when/how should we use them?
public class Volatile {
volatile int x = 0;
public static void main(String a[]) {
Volatile y = new Volatile();
test t1 = new test(y);
test t2 = new test(y);
t1.setName("A");
t2.setName("B");
t1.start();
t2.start();
}
}
class test extends Thread {
Volatile v;
test(Volatile v) {
this.v = v;
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread().getName() + "Says Before " + v.x);
v.x++;
System.out.println(Thread.currentThread().getName() + "Says After " + v.x);
}
}
}
输出
ASays Before 0
BSays Before 0
BSays After 2
BSays Before 2
BSays After 3
ASays After 1 <--- Is it a cache value ?
BSays Before 3
ASays Before 3
BSays After 4
BSays Before 5
BSays After 6
ASays After 5 <--- Is it a cache value ?
ASays Before 6
ASays After 7
ASays Before 7
ASays After 8
我到处都发现了关于 volatile 的共同点
guaranteeing that it will not be cached and that different threads
will see the updated value
但是,从我上面的例子来看,线程有不同的值(old/cache 值)还是因为实施不当?
将变量标记为 volatile 将阻止 JVM 缓存该值,但它 不会 为您处理同步问题(例如在修改变量之间换出线程)并打印出来)。
例如,线程 A 输出 before 0
然后被换出,以便线程 B 运行。该值仍然为零,因为 A 尚未更新它。 B 然后更新它,然后 A 返回并更新它,然后打印它。这意味着您最终可能会得到类似的结果:
ASays Before 0
BSays Before 0
ASays After 2
不太理想。
此外,println
本身不是原子的,所以它可以在流中途被中断,导致线程持有一个过时的打印值(即稍后出现在输出流中的值)那它会是理想的)。
要正确更新和打印,您应该在需要自动使用变量的块周围使用 synchronized
,例如:
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " before " + v.x);
v.x++;
System.out.println(Thread.currentThread().getName() + " after " + v.x);
}
然而,在您的特定情况下,您不能使用 this
,因为它是线程对象,这意味着它们有 两个 ,因此它们不会互相阻塞如你所愿。
你可以通过一点点麻烦来解决这个问题,在线程中引入一个静态对象 class:
static Object o = new Object();
并将其用于同步:
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " before " + v.x);
v.x++;
System.out.println(Thread.currentThread().getName() + " after " + v.x);
}
public class Volatile {
volatile int x = 0;
public static void main(String a[]) {
Volatile y = new Volatile();
test t1 = new test(y);
test t2 = new test(y);
t1.setName("A");
t2.setName("B");
t1.start();
t2.start();
}
}
class test extends Thread {
Volatile v;
test(Volatile v) {
this.v = v;
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread().getName() + "Says Before " + v.x);
v.x++;
System.out.println(Thread.currentThread().getName() + "Says After " + v.x);
}
}
}
输出
ASays Before 0
BSays Before 0
BSays After 2
BSays Before 2
BSays After 3
ASays After 1 <--- Is it a cache value ?
BSays Before 3
ASays Before 3
BSays After 4
BSays Before 5
BSays After 6
ASays After 5 <--- Is it a cache value ?
ASays Before 6
ASays After 7
ASays Before 7
ASays After 8
我到处都发现了关于 volatile 的共同点
guaranteeing that it will not be cached and that different threads will see the updated value
但是,从我上面的例子来看,线程有不同的值(old/cache 值)还是因为实施不当?
将变量标记为 volatile 将阻止 JVM 缓存该值,但它 不会 为您处理同步问题(例如在修改变量之间换出线程)并打印出来)。
例如,线程 A 输出 before 0
然后被换出,以便线程 B 运行。该值仍然为零,因为 A 尚未更新它。 B 然后更新它,然后 A 返回并更新它,然后打印它。这意味着您最终可能会得到类似的结果:
ASays Before 0
BSays Before 0
ASays After 2
不太理想。
此外,println
本身不是原子的,所以它可以在流中途被中断,导致线程持有一个过时的打印值(即稍后出现在输出流中的值)那它会是理想的)。
要正确更新和打印,您应该在需要自动使用变量的块周围使用 synchronized
,例如:
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " before " + v.x);
v.x++;
System.out.println(Thread.currentThread().getName() + " after " + v.x);
}
然而,在您的特定情况下,您不能使用 this
,因为它是线程对象,这意味着它们有 两个 ,因此它们不会互相阻塞如你所愿。
你可以通过一点点麻烦来解决这个问题,在线程中引入一个静态对象 class:
static Object o = new Object();
并将其用于同步:
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " before " + v.x);
v.x++;
System.out.println(Thread.currentThread().getName() + " after " + v.x);
}