我无法理解如果我的变量既是易失性的又是静态的,那么为什么线程不反映输出中的公共共享值 最后几行的输出是: 线程正在运行 4998Thread-0 线程正在运行 4999Thread-0 线程正在运行 4899Thread-1 线程正在运行
public class Test implements Runnable{
volatile static int i=0;
@Override
public void run() {
for(;i<5000;i++)
{ try {
Thread t = Thread.currentThread();
String name = t.getName();
// Thread.sleep(10);
System.out.println(i+name);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println("Thread is running");
}}
public static void main(String[] args) {
Test t=new Test();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
// t.run();
t2.start();
}
}
答案 0 :(得分:2)
您不能在易变量上使用i++
之类的复合(多步)操作。
您可以让两个线程检索该值,增加它并将其写回,从而导致一个丢失的增量(如您在示例中所示)。
答案 1 :(得分:0)
Volatile确保变量的更改对其他线程可见。但无法确保同步。
从程序的一次执行中我得到了这个输出:
Thread is running
3474Thread-1
Thread is running
3475Thread-0
Thread is running
3477Thread-0
(.... many lines where i goes from 3478 to 4998, with Thread-0 always being the one running...)
Thread is running
4999Thread-0
Thread is running
3476Thread-1
Thread is running
这是因为线程会获得运行处理器时间的片段,并且可以在任何时候暂停和恢复它们的执行。 这里Thread-1正在执行“System.out.println(i + name);”行我的值为3476.i + name被评估为“3476Thread-1”,但就在此之后,Thread-1执行停止,而Thread-0则获得其时间片。 Thread-0执行直到完成。然后Thread-1再次执行。我们已将 名称评估为“3476Thread-1”并且在调用println之前保留了。呼叫现在已完成并打印,因此您可以在此结束时看到“3476Thread-1”。我已经被Thread-0增加到5000,但是这并没有改变i + name的评估结果,这个结果是之前所有这些增加。
问题是i ++和i + name是不同的指令,线程执行可以暂停并在它们之间恢复。为了确保获得一个secuential输出,您需要确保i ++和i + name之间没有中断。也就是说,您需要使该组指令成为原子。
public class Test implements Runnable{
static Object lock = new Object();
volatile static int i=0;
@Override
public void run() {
for(;;)
{
try {
Thread t = Thread.currentThread();
String name = t.getName();
synchronized( lock )
{
if ( i>=5000 )
break;
i++;
System.out.println(i+name);
}
// Thread.sleep(10);
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Thread is running");
}
} }
public static void main(String[] args) {
Test t=new Test();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
// t.run();
t2.start();
}
}
在该程序中,如果Thread-1在i ++和i +名称之间暂停,它将位于由synchronized(lock)控制的临界区内。当Thread-0执行时,它将到达同步(锁定)指令,并且必须停止执行,直到Thread-1恢复并离开该块。因为JLS确保:
synchronized语句(第14.19节)计算对象的引用; 然后它尝试在该对象的监视器上执行锁定操作 在锁定操作成功之前不会继续进行 完成。锁定动作执行后,身体的 执行synchronized语句。如果身体的执行永远 完成,无论是正常还是突然,解锁动作都是 在同一台显示器上自动执行。
答案 2 :(得分:0)
这是一段代码片段,我从中学习了volatile关键字:
import java.util.concurrent.TimeUnit;
public class VolatileDemo {
private volatile static boolean stop; //remove volatile keyword to see the difference
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread otherThread = new Thread(new Runnable() {
public void run() {
while (!stop)
i++;
}
});
otherThread.start();
TimeUnit.SECONDS.sleep(1);
stop = true;// main thread stops here and should stop otherThread as well
}
}
如果你想观察volatile的作用,尝试删除它然后按照执行,这应该是你运行这两个版本后显而易见的,但基本上这里的关键字阻止java编译器假设停止条件永远不会改变,它会每次评估条件时都要读取。干净,不是吗?
通过查看代码,问题不在于使用volatile关键字,问题是表达式i++
不是原子的。它实际上是3步操作:
1) fetch value of i;
2) increment i;
3) save new value of i
当多线程发挥作用时,这些可能会或可能不会与其他线程的指令混合使用。 所以执行也可能看起来像这样:
1) T1: fetch i;
2) T2: fetch i;
3) T1: increment i;
4) T2: increment i;
5) T1: save i;
6) T2: save i;
如果i
为0
,您认为您将2
作为输出,而不是1
。
同步,这在没有过度使用和深思熟虑的情况下非常简单。
建议阅读synchronization
答案 3 :(得分:0)
如果两个线程都在读取和写入共享变量,那么使用volatile关键字是不够的。在这种情况下,您需要使用同步来保证变量的读取和写入是原子的。
因此,在修改i值时需要使用同步。