Java多线程与方法调用一起使用?

时间:2013-04-06 14:51:27

标签: java multithreading concurrency

我在互联网上遇到了以下Java类:

public class Lock1 implements Runnable {
int b=100;
public synchronized void m1() throws Exception {
    b=1000;
    Thread.sleep(50);
    System.out.println("b="+b);
}

public synchronized void m2() throws Exception {
    Thread.sleep(30);
    //System.out.println("m2");
    b=2000;
}

public void run() {
    try {m1();}
    catch(Exception e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) throws Exception {
    Lock1 tt=new Lock1();
    Thread t = new Thread(tt);
    t.start();

    tt.m2();
    System.out.println(tt.b);
}
}

尝试多次运行,结果几乎总是如此:

     1000
     b=1000

在我最初的猜测中,我认为第一行应该是“2000”,因为tt.m2()只是一个方法调用(不是一个线程),main方法应该继续执行并得到结果“b” “因为在方法m2中已经为其分配了值2000。

我做的第二次尝试是取消注释

 System.out.println("m2") 

以m2方式表示。结果几乎总是如此:

 m2
 2000
 b=1000

为什么在m2方法中添加语句会导致tt.b的输出值发生变化?

对不起,我在这里很困惑线程和方法调用之间的区别,希望专家能帮帮忙!

3 个答案:

答案 0 :(得分:5)

Java意义上的同步结合了几个方面。在这种情况下,这些要点很有趣:

  • 互斥
  • 读者的记忆障碍
  • 作家的记忆障碍

输入synchronized块(或方法)后,您有两个保证:您有锁(互斥),JVM和编译器将丢弃同步对象的的任何缓存。这意味着访问this.b将从RAM中获取“b”的实际值,而不是从任何缓存获取。然后它将再次使用缓存副本。

依次保留synchronized块可以保证CPU将所有脏(即写入)缓存刷新到内存中。

你的东西中的要点是:System.out.println(tt.b);绝不同步,这意味着对它的访问没有超过定义的内存障碍。因此,虽然另一个线程为b写了一个新值并将其刷新到RAM,但是主线程不知道它应该从RAM读取b而不是从它自己的缓存中读取。{/ p >

解决方案是:

synchronized(tt){
    System.out.println(tt.b);
}

这符合黄金法则,即如果某些内容同步,那么每次访问它都应该同步,而不仅仅是访问的一半。

关于你添加的System.out:有三件事:

第一:它很慢(与一些内存摆弄相比)。这意味着在此期间CPU或JVM可能自行决定tt的新外观可能是合适的

第二:它很大(与一些记忆混乱相比)。这意味着单独触摸的代码可能会从缓存中逐出tt

第三:内部同步。这意味着你克服了一些内存障碍(这可能与你的tt无关 - 谁知道)。但这些也可能会产生一些影响。

这是多线程调试的主要规则:根据Murphy的说法,为了捕获错误而添加System.out实际上会隐藏问题。

答案 1 :(得分:1)

我想这是特定于JVM的实现。

基本上,每个线程都有自己的对象变量副本(视图)以及它们来回同步的方式。

答案 2 :(得分:1)

最可能的原因是System.out.println很慢。 “意外”结果的原因是由于延迟(Thread.sleep)与打开输出流(System.out.println)的开销之间的竞争条件。