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


当前回答

我知道这是一个老问题,但通过快速阅读这里的回答,我并没有看到任何人提到同步方法有时可能是错误的锁。 摘自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的线程修改列表,直到同步块完成。

其他回答

同步的方法

同步方法有两个效果。 首先,当一个线程正在为一个对象执行同步方法时,所有为同一对象调用同步方法的其他线程将阻塞(暂停执行),直到第一个线程处理完该对象。

其次,当同步方法退出时,它自动与同一对象的同步方法的任何后续调用建立happens-before关系。这保证了对对象状态的更改对于所有线程都是可见的。

注意,构造函数不能同步——在构造函数中使用synchronized关键字是一个语法错误。同步构造函数没有意义,因为只有创建对象的线程才能在构造对象时访问它。

同步语句

与同步方法不同,同步语句必须指定提供内在锁的对象:大多数情况下,我使用它来同步对列表或映射的访问,但我不想阻塞对对象的所有方法的访问。

Q:内在锁和同步 同步是围绕一个称为内在锁或监视器锁的内部实体构建的。(API规范通常将此实体简单地称为“监视器”)内在锁在同步的两个方面都发挥作用:强制独占访问对象的状态,并建立对可见性至关重要的happens-before关系。

Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object's fields has to acquire the object's intrinsic lock before accessing them, and then release the intrinsic lock when it's done with them. A thread is said to own the intrinsic lock between the time it has acquired the lock and released the lock. As long as a thread owns an intrinsic lock, no other thread can acquire the same lock. The other thread will block when it attempts to acquire the lock.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

用同步方法,块和不同步交叉检查不同的输出。

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

同步方法可以使用反射API进行检查。这对于测试某些契约很有用,比如模型中的所有方法都是同步的。

下面的代码段打印哈希表的所有同步方法:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}

我知道这是一个老问题,但通过快速阅读这里的回答,我并没有看到任何人提到同步方法有时可能是错误的锁。 摘自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的线程修改列表,直到同步块完成。

唯一真正的区别是同步块可以选择在哪个对象上同步。同步方法只能使用'this'(或同步类方法的相应Class实例)。例如,它们在语义上是等价的:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

后者更灵活,因为它可以竞争任何对象(通常是成员变量)的关联锁。它也更细粒度,因为您可以在块之前和块之后执行并发代码,但仍然在方法中。当然,您也可以通过将并发代码重构为单独的非同步方法来轻松地使用同步方法。使用任何使代码更容易理解的方法。