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


当前回答

唯一的区别是:同步块允许颗粒状锁定,不像同步方法

基本上同步块或方法被用来编写线程安全的代码,以避免内存不一致的错误。

这个问题很老了,在过去的7年里,很多事情都发生了变化。 为了线程安全,引入了新的编程结构。

您可以通过使用高级并发API而不是同步块来实现线程安全。该文档页提供了实现线程安全的良好编程结构。

锁对象支持简化许多并发应用程序的锁定习惯用法。

executor为启动和管理线程定义了高级API。concurrent提供的执行器实现提供了适合大型应用程序的线程池管理。

并发集合使管理大型数据集合变得更容易,并且可以大大减少同步的需要。

原子变量具有最小化同步和帮助避免内存一致性错误的特性。

ThreadLocalRandom(在JDK 7中)提供了从多个线程有效生成伪随机数的功能。

更好的synchronized替代品是ReentrantLock,它使用Lock API

一个可重入互斥锁,其基本行为和语义与使用同步方法和语句访问的隐式监视锁相同,但具有扩展功能。

锁的例子:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

其他编程结构也可以参考java.util.concurrent和java.util.concurrent.atomic包。

参考这个相关的问题:

同步vs锁定

其他回答

在同步方法的情况下,锁将在对象上获得。但是如果你使用同步块,你可以选择指定一个对象来获取锁。

例子:

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }

关于使用同步块的重要提示:小心你使用的锁对象!

上面user2277816的代码片段说明了这一点,将字符串字面值的引用用作锁定对象。 意识到字符串字面值在Java中是自动互缩的,您应该开始看到问题所在:在字面值“锁”上同步的每段代码都共享同一个锁!这很容易导致完全不相关的代码段发生死锁。

您需要注意的不仅仅是String对象。装箱的原语也是一种危险,因为autoboxing和valueOf方法可以重用相同的对象,这取决于值。

有关更多信息,请参阅: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused

我想这个问题是关于线程安全单例和带有双重检查锁定的惰性初始化之间的区别。当我需要实现某些特定的单例时,我总是会参考这篇文章。

这是一个线程安全单例:

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

优点: 延迟初始化是可能的。 它是线程安全的。 缺点: getInstance()方法是同步的,因此它会导致性能变慢,因为多个线程不能同时访问它。

这是一个带有双重检查锁定的Lazy初始化:

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

优点: 延迟初始化是可能的。 它也是线程安全的。 克服了synchronized关键字导致的性能下降。 缺点: 第一次,它会影响性能。 由于双止回锁方法的缺点是可以承受的,所以可以 用于高性能多线程应用程序。

详情请参考这篇文章:

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/

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

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

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

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