从非同步方法内部调用同步

时间:2014-06-03 13:10:09

标签: java thread-safety synchronized

我正在尝试使用Threads并且它没有按预期工作,尽管我已经应用了同步。

我有一个方法

private synchronized void printStatus(){
        System.out.println("\t\t\t" + Thread.currentThread().getName());
        System.out.println("\t\t\tCookies: " + contents);
}

所以我期待,当有人打电话时,在输出中我会得到一行接一行。但事实并非如此。我的输出如下:

            homerThread
            Cookies: 0
            margeThread
0 cookies were Removed
            Cookies: 0
            homerThread
3 cookies were Put
            Cookies: 3
0 cookies were Removed
            margeThread
            Cookies: 3
3 cookies were Put
            homerThread
            Cookies: 6
4 cookies were Removed
            margeThread
            Cookies: 2
1 cookies were Put
            homerThread
            Cookies: 3
3 cookies were Removed
                margeThread
                Cookies: 0
....

如您所见,有很多行未正确同步

为什么会这样?


为了完整起见,我已将我的整个代码包括在内;

Main.java类

public class Main {
    public static void main(String[] args) { 

        CookieJar jar = new CookieJar(); 

        Homer homer = new Homer(jar); 
        Marge marge = new Marge(jar); 

        Thread homerThread = new Thread(homer);
        homerThread.setName("homerThread");
        Thread margeThread = new Thread(marge);
        margeThread.setName("margeThread");

        homerThread.start();
        margeThread.start();
    } 

}

Homer.java

import java.util.Random;

public class Homer implements Runnable {

    CookieJar jar; 

    public Homer(CookieJar jar) { 
        this.jar = jar; 
    } 
    public void eat(int amnt) { 
        jar.getCookie(amnt); 
    } 
    public void run() { 
        Random random = new Random();
        for(int i = 0; i < 10; i++){
            eat(random.nextInt(5));
        } 
    } 
}

玛吉

import java.util.Random;

public class Marge implements Runnable {
    CookieJar jar; 
    public Marge(CookieJar jar) { 
        this.jar = jar; 
    } 
    public void bake(int cookie) { 
        jar.putCookie(cookie); 
    } 
    public void run() { 
        Random random = new Random();
        for(int i = 0; i < 10; i++){
            bake(random.nextInt(5));
        }
    } 


}

CookieJar.java

public class CookieJar {
    int contents = 0; 
    boolean hasCookie = false; 

    public void putCookie(int amount) {  
        printStatus();
        contents += amount;
        System.out.println(amount + " cookies were Put");
    } 

    public void getCookie(int amount) { 
        printStatus();
        contents -= amount;
        System.out.println(amount + " cookies were Removed");
    }

    private synchronized void printStatus(){
        System.out.println("\t\t\t" + Thread.currentThread().getName());
        System.out.println("\t\t\tCookies: " + contents);
    }
}

*注意:是的我知道荷马可能会吃掉负数量的饼干,这可能是也可能是不可能的。

3 个答案:

答案 0 :(得分:2)

问题是这些行:

System.out.println(amount + " cookies were Put");

System.out.println(amount + " cookies were Removed");

jar上未同步。如果您只查看{em> 同步的xThreadCookies: N输出,那么这些输出始终是正确排序的。

答案 1 :(得分:2)

您还应该synchronize使用getCookieputCookie方法,否则它们会与printStatus方法交错。

答案 2 :(得分:1)

好的,这是synchronized的作用:它可以防止两个或多个线程同时在同一个对象上同步。它没有任何其他。它不会阻止两个线程同时进入同一个同步方法(线程可能在不同的实例上调用相同的方法)。在对象上进行同步不会阻止其他线程修改该对象。 (其他线程可能采用非同步方法)。

如果要阻止其他线程在printStatus()打印的两行之间打印消息,那么仅仅同步printStatus()是不够的。您必须同步可以使用System.out的每个线程。我就是这样做的:

private void printStatus() {
    synchronized (System.out) {
        System.out.println("\t\t\t" + Thread.currentThread().getName());
        System.out.println("\t\t\tCookies: " + contents);
    }
}

public void putCookie(int amount) {  
    printStatus();
    contents += amount;
    synchronized (System.out) {
        System.out.println(amount + " cookies were Put");
    }
}

...

我在这里同步System.out以说明一点。 System.out是我要保护的东西如果尝试写入System.out的每个方法都是从synchronized (System.out)内部执行的,那么我的程序中有哪些其他线程和其他同步对象并不重要,那么输出都将被正确交错。


额外信用:

在生产代码中,我会这样做:

private static Object consoleLock = new Object();

...
    synchronized (consoleLock) {
        System.out.println(...);
        ...
    }
...

只要我始终在任何地方使用consoleLock,它将与在System.out上同步具有相同的效果,但它具有锁变量为private的优点。这样,我知道没有其他程序员会因为其他原因而在我的锁上同步。 (由于任何其他原因,他们必须非常疯狂地在System.out上进行同步,但那时一些疯狂的开发人员。)

另请注意:我使consoleLock静态,因为System.out是静态的。只有一个System.out,所以我只有一个锁对象很重要。