同步锁对象和使用this作为锁之间有什么区别?

56

我知道同步方法和同步块之间的区别,但我不确定同步块部分。

假设我有以下代码:

class Test {
  private int x=0;
  private Object lockObject = new Object();

  public void incBlock() {
    synchronized(lockObject) {
      x++;
    }
    System.out.println("x="+x);
  }

  public void incThis() {  // same as synchronized method
    synchronized(this) {
      x++;
    }
    System.out.println("x="+x);
  }
}
在这种情况下,使用 lockObject 和使用 this 作为锁有什么区别?在我看来似乎是一样的。
当你决定使用同步块时,如何决定哪个对象作为锁?

6
我在 https://dev59.com/XXA75IYBdhLWcg3w49YR 中找到了答案。如果你看看答案,就会清楚地发现:如果有两个线程(t1 和 t2),其中 t1 调用 x.addA(),而 t2 调用 x.addB()。如果 addA 和 addB 都使用 this 作为锁,那么 x.addA() 和 x.addB() 就不能同时运行。而如果 addA 和 addB 使用不同的对象作为锁,则两个方法可以同时运行。 - GantengX
2
只是确认一下:在上面的例子中,当你使用lockObject来有效地保护对x的访问时,是否存在这样一种情况,即在同步块之后但在println和increase x之前,另一个线程可能会进入?也就是说,你真的需要在同步块中打印输出吗? - Component 10
1
啊,对了,我应该把它放在同步块中,但是我已经回答了自己的问题:D - GantengX
1
@GantengX 让我澄清一下。使用私有对象作为锁可以确保使用Test类的相同实例的线程在同时访问该方法时得到同步。如果有多个Test类的实例,锁对象将是不同的。即使用单独实例的线程将不会被同步(除非有多个线程使用相同的实例)。这正确吗? - Preslav Rachev
1
@user1107412 是的,没错。使用私有对象的原因是其他对象不能使用相同的锁(不像使用 this)。 - GantengX
5个回答

74

个人而言,我几乎从不锁定"this"。我通常锁定一个私有的引用,我知道没有其他代码会锁定它。如果你锁定了"this",那么任何知道你的对象的其他代码都可能选择锁定它。虽然这种情况不太可能发生,但肯定可能会发生——并可能导致死锁或过多的锁定。

你锁定的东西没有什么特别神奇之处——你可以将其视为令牌。使用相同令牌加锁的任何人都将尝试获取相同的锁。除非你让其他代码能够获取相同的锁,请使用私有变量。我还鼓励你将变量设置为final——在对象的生命周期内,我无法记得自己曾经想过更改锁变量的情况。


你能详细解释一下吗? 假设类 A 拥有类 B 的实例。现在 B 有一些成员变量。当 A 调用 B 的任何方法时,它会获取该/实例成员的锁...对吧?这意味着获取了 B 实例本身或其成员的锁...你能解释一下区别吗? - Parth
1
@Paarth:你的评论不太清楚 - 你是指 B 应该在每个方法上锁定私有锁吗?这取决于类本身的目的。说实话,大多数类根本不需要尝试成为线程安全的。 - Jon Skeet
哦,所以如果它需要制作同步方法,它应该使用私有成员对象而不是 'this'... 对吗?好的... - Parth
synchronised(new Object) 和 synchronised(this),它们之间的实际区别是什么?在什么情况下使用哪一个? - Maheshwar Ligade
1
@MaheshwarLigade:你绝对不应该仅为了一个synchronized块而创建一个新对象——因为没有其他代码可以针对同一对象进行同步。通常情况下,你应该在类中创建一个final变量,例如private final Object lock = new Object();,并针对进行同步。这比针对this进行同步更好,因为你知道只有你的代码才能访问该监视器以进行同步。 - Jon Skeet

14

我在阅读《Java并发实践》时也有同样的问题,我想为Jon Skeet和spullara提供的答案添加一些额外的观点。

以下是一些示例代码,即使是“快速”的setValue(int) / getValue()方法在执行doStuff(ValueHolder)方法时也会被阻塞。

public class ValueHolder {
    private int value = 0;

    public synchronized void setValue(int v) {
        // Or could use a sychronized(this) block...
        this.value = 0;
    }

    public synchronized int getValue() {
        return this.value;
    }
}

public class MaliciousClass {

    public void doStuff(ValueHolder holder) {
        synchronized(holder) {
            // Do something "expensive" so setter/getter calls are blocked
        }
    }
}
使用 this 进行同步的缺点是其他类可以通过对你的类的引用(当然不是通过 this)进行同步。在锁定对象引用时恶意或无意使用 synchronized 关键字可能会导致你的类在并发使用下表现不佳,因为外部类可以有效地阻止你的 this 同步方法,你不能在运行时在你的类中禁止这种情况的发生。为避免这种潜在陷阱,你可以同步使用一个private final Object 或者使用 java.util.concurrent.locks 中的 Lock 接口。
对于这个简单的示例,你也可以使用 AtomicInteger 来代替同步 setter/getter。

6

3
不能回答问题(使用synchronizedLocks),没有真正的解释,链接损坏(存档版本)。被踩。 - try-catch-finally

2

Java中的每个对象都可以充当监视器。选择取决于您需要的粒度。选择“this”的优点和缺点是其他类也可以在同一监视器上进行同步。然而,我的建议是避免直接使用synchronize关键字,而是使用来自java.util.concurrent库的构造,这些构造级别更高,并且具有明确定义的语义。这本书中有很多很棒的建议来自非常著名的专家:

Java并发编程实践 http://amzn.com/0321349601


0
在这种情况下,选择哪个对象作为锁并不重要。但是,为了实现正确的同步,您必须始终使用相同的对象进行锁定。上面的代码不能确保正确的同步,因为您一次使用'this'对象作为锁,下一次使用'lockObject'作为锁。

我明白这不是一种适当的同步方式,因为它对于不同的方法使用了不同的锁,我只是想理解在锁定时使用“this”和“lockObject”的区别。 - GantengX
哦,抱歉误解了你的问题。所以对于这个问题,答案与Jon Skeet所回答的相同。 - Gopi

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