在Java中如何选择要锁定的对象?

4
我已经阅读了类似问题的答案,了解了如何正确使用 synchronized。然而,它们似乎没有解释为什么会出现这个问题。
即使我在 getValue 和 setValue 方法中添加了 synchronized,我仍然会得到以下输出。
为什么会发生这种情况呢? 输出:

making set

Doing get

making set

Doing making set

get

Doing get

making set

making Doing get

set

代码:
package src;

public class StackNode {
private Object value;
private StackNode next;

private final Object lock = new Object();

public StackNode() {
    setValue(null);
    setNext(null);
}

public StackNode(Object o) {
    value = 0;
    next = null;
}

public StackNode(StackNode node) {
    value = node.getValue();
    next = node.getNext();
}

public synchronized Object getValue() {
        System.out.print(" Doing ");
        System.out.println(" get ");
        System.out.flush();
        return value;

}

public  synchronized void setValue(Object value) {
        System.out.print(" making ");
        System.out.println(" set ");
        System.out.flush();
        this.value = value;
}

public synchronized StackNode getNext() {
    return next;
}

public synchronized void setNext(StackNode next) {
    this.next = next;
}
}

测试:

public class TestStackNode {
private final static StackNode node = new StackNode();

    @Test
public void getSetValueTest() throws InterruptedException{
    node.setValue("bad");
    Runnable setValue = new Runnable(){
        @Override
        public void run() {
            node.setNext(new StackNode());
            node.setValue("new");
        }
    };
    
    Runnable getValue = new Runnable(){
        @Override
        public void run() {
            Assert.assertEquals("new", node.getValue());
        }
    };
    List<Thread> set = new ArrayList<Thread> ();
    List<Thread> get = new ArrayList<Thread> ();
    for (int i = 0; i < 30000; i++){
        set.add( new Thread(setValue));
        get.add(new Thread(getValue));
    }
    
    for (int i = 0; i < 30000; i++){
        set.get(i).start();
        get.get(i).start();
    }
    
    for (int i = 0; i < 30000; i++){
        set.get(i).join();
        get.get(i).join();
    }
}

1
同步方法相互交错,因此我得到的输出类似于 making doing get [new line] set。 - user2714786
1
你确定两个线程都在使用同一个对象吗? - Dawood ibn Kareem
是的,我还发布了我的 JUnit 测试代码,你可以看一下。 - user2714786
请发布完整的测试用例。 - chrylis -cautiouslyoptimistic-
是的。整个测试已经发布了。去看看吧。 - user2714786
显示剩余2条评论
2个回答

4

这应该可以解决问题。

public Object getValue() {
  synchronized(System.out){
    System.out.print(" Doing ");
    System.out.println(" get ");
    System.out.flush();
    return value;
  }

}

完美。问题已解决。问题出在打印顺序上,线程运行正常。 - Lokesh
1
从字面上理解,这显然是不正确的——在System.out上同步一个方法,而在StackNode实例上同步另一个方法,几乎保证输出交错——但如果您添加一些关于您的目标的解释,也许这个答案就可以变得有用了? - ruakh
哦,我的意思是用 System.out 锁定所有 Setter 和 Getter,而不是使用 synchronized 关键字。 - user1559897

2
问题在于您的无参构造函数在新创建的实例上调用了 setValue(...)
public StackNode() {
    setValue(null);
    setNext(null);
}

你的 Runnable setValue 构造一个新的 StackNode 实例,以传递给 node.setNext(...)

            node.setNext(new StackNode());

尽管你的测试从未实际使用node.next,因此除了产生输出之外,这基本上是一个无操作。由于你的synchronized方法是实例方法(而不是static方法),它们具有单独的锁,这意味着在新实例构造函数中对setValue(...)的调用与你在node上进行的调用不同步。

请注意,尽管你的特定问题相当不寻常(你有一个getter和setter正在操作共享的外部状态,即System.out,但没有任何相应的共享锁来防止干扰),但从构造函数调用方法实际上总是一个坏主意,除非该方法是privatefinalstatic,或者该类是final,因为在子类实例完全创建之前会先调用超类构造函数,因此如果构造函数调用了一个在子类中被覆盖的方法,则子类方法将接收到不完整的this对象,并可能表现得非常糟糕。最好将你的构造函数更改为:

public StackNode() {
    value = null;
    next = null;
}

(或者干脆删除赋值语句,因为引用类型的字段会自动初始化为null)。

非常棒的分析。问题是“为什么会发生这种情况”,而你提供了一个很好的答案。 - Dawood ibn Kareem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接