【问题标题】:Why is synchronized block better than synchronized method?为什么同步块比同步方法更好?
【发布时间】:2014-01-21 07:03:23
【问题描述】:

我已经开始学习线程同步了。

同步方式:

public class Counter {

   private static int count = 0;

   public static synchronized int getCount() {
      return count;
   }

   public synchronized setCount(int count) {
      this.count = count;
   }

}

同步块:

public class Singleton {

   private static volatile Singleton _instance;

   public static Singleton getInstance() {
      if (_instance == null) {
         synchronized(Singleton.class) {
            if (_instance == null)
               _instance = new Singleton();
         }
      }
      return _instance;
   }
}

什么时候应该使用synchronized 方法和synchronized 块?

为什么synchronized 块比synchronized 方法更好?

【问题讨论】:

  • 在第一种情况下,我将使用 AtomicInteger,在第二种情况下,我将 enum 用于 Singleton。
  • 哪里说的比较好?您提出问题的依据是什么?

标签: java multithreading synchronization


【解决方案1】:

同步块和同步方法之间的一个典型区别是同步方法锁定整个对象。同步块只是将代码锁定在块内。

同步方法:基本上这两种同步方法禁用多线程。所以一个线程完成method1(),另一个线程等待Thread1完成。

类 SyncExerciseWithSyncMethod {

public synchronized void method1() {
    try {
        System.out.println("In Method 1");
        Thread.sleep(5000);
    } catch (Exception e) {
        System.out.println("Catch of method 1");
    } finally {
        System.out.println("Finally of method 1");
    }

}

public synchronized void method2() {
    try {
        for (int i = 1; i < 10; i++) {
            System.out.println("Method 2 " + i);
            Thread.sleep(1000);
        }
    } catch (Exception e) {
        System.out.println("Catch of method 2");
    } finally {
        System.out.println("Finally of method 2");
    }
}

}

输出

在方法一中

方法一的最后

方法2 1

方法2 2

方法2 3

方法2 4

方法2 5

方法2 6

方法2 7

方法2 8

方法2 9

方法二的最后


同步块:允许多个线程同时访问同一个对象[启用多线程]。

类 SyncExerciseWithSyncBlock {

public Object lock1 = new Object();
public Object lock2 = new Object();

public void method1() {
    synchronized (lock1) {
        try {
            System.out.println("In Method 1");
            Thread.sleep(5000);
        } catch (Exception e) {
            System.out.println("Catch of method 1");
        } finally {
            System.out.println("Finally of method 1");
        }
    }

}

public void method2() {

    synchronized (lock2) {
        try {
            for (int i = 1; i < 10; i++) {
                System.out.println("Method 2 " + i);
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.out.println("Catch of method 2");
        } finally {
            System.out.println("Finally of method 2");
        }
    }
}

}

输出

在方法一中

方法2 1

方法2 2

方法2 3

方法2 4

方法2 5

方法一的最后

方法2 6

方法2 7

方法2 8

方法2 9

方法二的最后

【讨论】:

    【解决方案2】:

    区别在于获取的是哪个锁:

    • 同步方法获取整个对象的锁。这意味着当方法由一个线程运行时,没有其他线程可以使用整个对象中的任何同步方法。

    • 同步块在同步关键字之后的括号之间的对象中获取锁。这意味着在同步块退出之前,没有其他线程可以获取锁定对象上的锁。

    因此,如果您想锁定整个对象,请使用同步方法。如果您想保持对象的其他部分可供其他线程访问,请使用同步块。

    如果你仔细选择锁定的对象,同步块将导致更少的争用,因为整个对象/类没有被阻塞。

    这同样适用于静态方法:同步的静态方法将在整个类对象中获取锁,而静态方法中的同步块将在括号之间的对象中获取锁。

    【讨论】:

    • 如果我有一个同步块并且一个线程正在其中运行,另一个线程可以进入对象并在对象的其他地方运行同步块吗?如果我有一个同步方法并且一个线程正在其中执行,那么没有其他线程可以在该对象中执行或只能在对象的同步区域中执行?
    【解决方案3】:

    同步块和同步方法的区别如下:

    1. 同步块减少了锁的范围,但是同步方法的锁范围是整个方法。
    2. 同步块具有更好的性能,因为只有关键部分被锁定但是同步方法的性能比块差。
    3. 同步块提供对锁的精细控制但同步方法锁在此表示的当前对象或类级别锁上。
    4. 同步块可以抛出 NullPointerException 但同步方法不会抛出。
    5. 同步块: synchronized(this){}

      同步方法: public synchronized void fun(){}

    【讨论】:

      【解决方案4】:

      这不应该被认为是最适合使用的问题,但它确实取决于用例或场景。

      同步方法

      可以将整个方法标记为同步,从而对 this 引用(实例方法)或类(静态方法)产生隐式锁定。这是实现同步的非常方便的机制。

      步骤 线程访问同步方法。它隐式获取锁并执行代码。 如果其他线程想要访问上述方法,它必须等待。线程无法获得锁,将被阻塞,必须等待锁被释放。

      同步块

      要为一组特定的代码块获取对象锁定,同步块是最合适的。由于一个块就足够了,使用同步方法将是一种浪费。

      更具体地说,使用同步块,可以定义要获取锁的对象引用。

      【讨论】:

        【解决方案5】:

        虽然通常不是问题,但从安全角度来看,最好在私有对象上使用同步,而不是将其放在方法上。

        将它放在方法上意味着您正在使用对象本身的锁来提供线程安全。使用这种机制,您的代码的恶意用户也有可能获得您对象的锁定,并永久持有它,从而有效地阻塞其他线程。非恶意用户可以在不经意间有效地做同样的事情。

        如果您使用私有数据成员的锁,则可以防止这种情况发生,因为恶意用户不可能获得您私有对象的锁。

        private final Object lockObject = new Object();
        
        public void getCount() {
            synchronized( lockObject ) {
                ...
            }
        }
        

        在 Bloch 的 Effective Java (2nd Ed),Item #70 中提到了这种技术

        【讨论】:

        • 甚至不必是恶意的,使用从某个地方获得的对象来锁定是非常容易和无辜的(比如说因为你想同步访问这个对象)。最好避免此类问题
        • @wolfcastle:你能解释一下什么是“你的代码的恶意用户”吗?他们如何通过弄乱自己的应用程序来恶意?
        • @ErickRobertson Image 您正在通过公共 API 提供某种服务。如果你的 API 暴露了一些可变对象,其操作依赖于锁定该对象,一个恶意客户端可以获取该对象,锁定它,然后永远循环,持有锁定。这可能会阻止其他客户端正常运行。它基本上是一种拒绝服务类型的攻击。所以他们不会只是搞砸自己的应用程序。
        • 虽然这是一个旧答案,但我对这个答案有评论。 “恶意用户不可能获得对您私人对象的锁定。” -- 如果“恶意用户”在synchronized 块中使用不同的对象而不是“您的私有对象”怎么办?
        • 这项技术在 Effective Java (3rd Edition) 中的 #82 项中
        【解决方案6】:

        因为锁很昂贵,所以当你使用同步块时,只有在_instance == null 时才会锁定,而在_instance 最终初始化之后,你将永远不会锁定。但是,当您在方法上同步时,您会无条件锁定,即使在 _instance 初始化之后也是如此。这就是双重检查锁定优化模式http://en.wikipedia.org/wiki/Double-checked_locking 背后的思想。

        【讨论】:

          【解决方案7】:

          同步的应该只在你希望你的类是线程安全的时候使用。事实上,大多数类无论如何都不应该使用同步。 同步方法只会在这个对象上提供一个锁,并且只在其执行期间。如果你真的想让你的类线程安全,你应该考虑让你的变量 volatile 或 synchronize 访问。

          使用同步方法的一个问题是类的所有成员都会使用相同的锁,这会使你的程序变慢。在您的情况下,同步方法和块将执行没有什么不同。我会建议使用专用的锁并使用类似的同步块。

          public class AClass {
          private int x;
          private final Object lock = new Object();     //it must be final!
          
           public void setX() {
              synchronized(lock) {
                  x++;
              }
           }
          }
          

          【讨论】:

            【解决方案8】:

            定义“更好”。同步块更好,因为它允许您:

            1. 在不同的对象上同步
            2. 限制同步范围

            现在您的具体示例是一个可疑的 double-checked locking 模式示例(在较旧的 Java 版本中它被破坏了,而且很容易出错)。

            如果您的初始化成本较低,最好立即使用 final 字段进行初始化,而不是在第一次请求时进行初始化,这样也无需同步。

            【讨论】:

              【解决方案9】:

              在你的情况下两者是等价的!​​strong>

              同步一个静态方法相当于在对应的Class对象上同步一个块。

              其实当你声明一个同步的静态方法时,锁是在类对象对应的监视器上获得的。

              public static synchronized int getCount() {
                  // ...
              }
              

              与

              相同
              public int getCount() {
                  synchronized (ClassName.class) {
                      // ...
                  }
              }
              

              【讨论】:

              • 好的。如果我同步一个非静态方法,那么结果会是什么
              • 那么它是每个实例或您创建的对象。两个线程可以在两个相互独立的不同对象中处理相同的方法。
              【解决方案10】:

              不是更好,只是不同。

              当你同步一个方法时,你实际上是在同步到对象本身。在静态方法的情况下,您正在同步到对象的类。所以下面两段代码的执行方式相同:

              public synchronized int getCount() {
                  // ...
              }
              

              这就像你写的一样。

              public int getCount() {
                  synchronized (this) {
                      // ...
                  }
              }
              

              如果您想控制与特定对象的同步,或者您只想将方法的部分同步到该对象,则指定synchronized 块。如果在方法声明中使用synchronized关键字,它会将整个方法同步到对象或类。

              【讨论】:

              • 是的,类变量和方法也是如此,除了获取相应类对象的监视器而不是实例(this)的监视器。
              • 是的,它是不同的,但是有很好的理由说明为什么你不想在 this 上同步,所以这个答案有点忽略了 imo。
              • @Voo:我强烈反对您永远想要在this 上同步。事实上,当你想连续调用一堆同步方法时,这是一个好处。您可以在持续时间内同步对象,而不必担心为每个方法调用释放和重新获取锁。有许多不同的模式可用于同步,根据具体情况,每种模式都有自己的优缺点。
              • 同步方法和唯一顶级块为synchronized(someObj)的方法之间绝对没有区别,实际上编译器为同步生成代码就好像someObj==this .所以这样做没有任何好处,但是您将内部细节暴露给外部世界,这显然会破坏封装。有一个好处:你节省了大约 20 字节。
              • @Voo 类的线程行为不是内部细节。这是您的代码合同的一部分。它经常被省略,但我是否可以在并发环境中使用一个类可能是它使用的很大一部分。 synchronized 关键字确实会泄露您管理线程的如何。我不认为这很糟糕,因为您可以更改线程控制并删除关键字而不会破坏任何客户端代码。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-13
              • 1970-01-01
              • 1970-01-01
              • 2016-03-02
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多