为什么这种同步不起作用?

时间:2014-11-22 08:54:49

标签: java multithreading synchronization sync runnable

这是我的代码:

public class TestClass {
    public static void main(String[] args) throws Exception {
        Thread threadOne = new Thread(new SomeRunnable("x"));
        Thread threadTwo = new Thread(new SomeRunnable("y"));
        threadOne.start();
        threadTwo.start();
    }
}

public class SomeRunnable implements Runnable {
    private String name;
    public SomeRunnable(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        for(int i=0;i<50;i++) {
            NameShouter.shoutName(name);
        }
    }
}

public class NameShouter {
    public static void shoutName(String name) {
        synchronized (System.out) {
            System.out.print(name);
        }
    }
}

我将得到的输出是:

xxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

我期待System.out将被同步,但事实并非如此。为什么?我怎样才能使这段代码能够输出:

... XXXXXXXXXXX YYYYYYYY ....

2 个答案:

答案 0 :(得分:2)

您正在同步shoutName的每个调用,这意味着每个名称都在下一个名称之前完全写入。如果你想一个接一个地喊出所有相同的名字,你需要在循环周围移动synchronized

public class SomeRunnable implements Runnable {
    private String name;
    public SomeRunnable(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        synchronized (System.out) {
           for(int i=0;i<50;i++) {
                NameShouter.shoutName(name);
            }
        }
    }
}

public class NameShouter {
    public static void shoutName(String name) {
        System.out.print(name);
    }
}

既然你提到你可能有不同的NameShouter来自不同的东西(并且你仍然需要能够喊出这些名字50次),你可能会引入一个多次喊叫的方法重载,传递PrintStream以用作参数:

public static void shoutName(String name, int count, PrintStream out) {
    synchronized (out) {
        out.print(name);
    }
}

然后,您只需针对当前行为调用shoutName("x", 50, System.out)

随着时间的推移,你会注意到只有静态方法会让事情变得麻烦,你可以使用NameShouter对象,而这些对象对于他们正在呼喊的资源具有适当的属性以及其他任何必要的对象。

如果您的资源不仅仅是PrintStream,例如如果你想要将图像的名字大喊大叫,也许,然后在PrintStream中包装你想要喊出的任何其他东西(可能不是最好的想法,因为滥用某些东西的界面会使它有点难以实现) ,或创建自己的界面,例如

public interface IResource {
    void print();
}

并将System.out以及您正确使用的其他内容包装到IResource

答案 1 :(得分:2)

好的尝试这样的事情:

public class TestClass {
public static void main(String[] args) throws Exception {
    TestClass test = new TestClass();
    Thread threadOne = new Thread(new SomeRunnable("x",test ));
    Thread threadTwo = new Thread(new SomeRunnable("y",test ));
    threadOne.start();
    threadTwo.start();
}
}

public class SomeRunnable implements Runnable {
private String name;
private TestClass test;
public SomeRunnable(String name, TestClass test) {
    this.name = name;
    this.test = test;
}
@Override
public void run() {
   synchronized(test){
    for(int i=0;i<50;i++) {
        NameShouter.shoutName(name);
    }
  }
}
}

public class NameShouter {
public static void shoutName(String name) {      
        System.out.print(name);        
}
}

如果您不想创建TestClass对象,可以执行以下操作:

public class Try {
public static void main(String[] args) throws Exception {
Try test = new Try();
Thread threadOne = new Thread(new SomeRunnable("x",Try.class));
Thread threadTwo = new Thread(new SomeRunnable("y",Try.class));
threadOne.start();
threadTwo.start();
}
}

class SomeRunnable implements Runnable {
private String name;
private Class test;
public SomeRunnable(String name, Class test) {
this.name = name;
this.test = test;
}
@Override
public void run() {
synchronized(test){
for(int i=0;i<50;i++) {
NameShouter.shoutName(name);
}
}
}
}