有人能告诉我同步方法比同步块的优势与一个例子吗?


当前回答

As already said here synchronized block can use user-defined variable as lock object, when synchronized function uses only "this". And of course you can manipulate with areas of your function which should be synchronized. But everyone says that no difference between synchronized function and block which covers whole function using "this" as lock object. That is not true, difference is in byte code which will be generated in both situations. In case of synchronized block usage should be allocated local variable which holds reference to "this". And as result we will have a little bit larger size for function (not relevant if you have only few number of functions).

你可以在这里找到更详细的解释: http://www.artima.com/insidejvm/ed2/threadsynchP.html

其他回答

TLDR;不要使用synchronized修饰符或synchronized(this){…}表达式but synchronized(myLock){…其中myLock是一个持有私有对象的最终实例字段。


在方法声明中使用synchronized修饰符与在方法主体中使用synchronized(..){}表达式的区别如下:

The synchronized modifier specified on the method's signature is visible in the generated JavaDoc, is programmatically determinable via reflection when testing a method's modifier for Modifier.SYNCHRONIZED, requires less typing and indention compared to synchronized(this) { .... }, and (depending on your IDE) is visible in the class outline and code completion, uses the this object as lock when declared on non-static method or the enclosing class when declared on a static method. The synchronized(...){...} expression allows you to only synchronize the execution of parts of a method's body, to be used within a constructor or a (static) initialization block, to choose the lock object which controls the synchronized access.

然而,使用synchronized修饰符或synchronized(…){…}使用this作为锁对象(如synchronized(this){…}),也有同样的缺点。两者都使用它自己的实例作为锁对象进行同步。这是很危险的,因为不仅对象本身,而且任何其他持有该对象引用的外部对象/代码也可以将其用作同步锁,这可能会产生严重的副作用(性能下降和死锁)。

因此,最佳实践是既不使用synchronized修饰符,也不使用synchronized(…)表达式作为锁对象,而是使用该对象的私有锁对象。例如:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

您也可以使用多个锁对象,但是需要特别注意,以确保在嵌套使用时不会导致死锁。

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}

我知道这是一个老问题,但通过快速阅读这里的回答,我并没有看到任何人提到同步方法有时可能是错误的锁。 摘自Java并发实践(第72页):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

上面的代码看起来是线程安全的。然而,现实并非如此。在这种情况下,锁在类的实例上获得。但是,该列表可能被另一个不使用该方法的线程修改。正确的方法是使用

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

上面的代码将阻止所有试图修改list的线程修改列表,直到同步块完成。

主要的区别是,如果你使用同步块,你可以锁定一个对象,而不是这个,这允许更灵活。

假设您有一个消息队列和多个消息生产者和消费者。我们不希望生产者相互干扰,但是消费者应该能够检索消息,而不必等待生产者。 我们只需要创建一个对象

Object writeLock = new Object();

从现在开始,每当制作人想要添加一条新信息时,我们就会锁定它:

synchronized(writeLock){
  // do something
}

因此,消费者可能仍会阅读,而生产者将被锁定。

使用同步块,您可以有多个同步器,因此多个同时但不冲突的事情可以同时进行。

在实际应用中,同步方法相对于同步块的优势在于它们更能抵抗白痴;因为您不能选择任意对象来锁定,所以您不能滥用synchronized方法语法来做一些愚蠢的事情,比如锁定字符串文字或锁定从线程下面更改的可变字段的内容。

另一方面,使用同步方法,您无法保护锁不被任何可以获得对象引用的线程获取。

因此,在方法上使用synchronized作为修饰符可以更好地保护你的奶牛免受伤害,而将synchronized块与私有final锁对象结合使用则可以更好地保护你自己的代码免受奶牛的伤害。