为什么同步块比同步方法更好?


75

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

同步方法:

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方法更好?



1
在第一种情况下,我将使用AtomicInteger;在第二种情况下,我将使用Single enum
彼得·劳瑞

它在哪里说更好?您的问题依据是什么?
user207421 '16

Answers:


89

这不是更好的问题,只是有所不同。

同步方法时,实际上是在与对象本身进行同步。对于静态方法,您正在同步到对象的类。因此,以下两段代码以相同的方式执行:

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

就像您写的一样。

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

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


是的,对于类变量和方法,除了获得对相应Class对象的监视而不是实例(this)的监视之外,其他方法都一样。
Aniket Thakur 2014年

7
是的,它有所不同,但是有一个很好的理由说明为什么您永远不想进行同步,this因此此答案有点错了。
Voo

4
@Voo:我非常不同意您永远都不想在上进行同步this。实际上,当您要连续调用一堆同步方法时,这是一个好处。您可以在持续时间内在对象上进行同步,而不必担心为每个方法调用释放和重新获取锁。有许多不同的模式可用于同步,每种模式根据情况都有其优缺点。
埃里克·罗伯逊

2
同步方法和仅顶级块的方法之间绝对没有区别synchronized(someObj),实际上,编译器生成的代码似乎是同步的someObj==this。因此,这样做没有任何优势,但是您将内部细节暴露给外部世界,这显然破坏了封装。好吧,这有一个好处:您节省了大约20个字节。
Voo

3
@Voo类的线程行为不是内部细节。这是您的代码合同的一部分。它经常被省略,但是我是否可以在并发环境中使用类可能是其中很大一部分。synced关键字确实泄漏如何管理线程。我并不认为这很糟糕,因为您可以更改线程控制并删除关键字而不会破坏任何客户端代码。
斯宾娜2014年

45

从安全角度来看,尽管通常不必担心,但最好在私有对象上使用同步,而不是将同步放在方法上。

将其放在方法上意味着您正在使用对象本身的锁来提供线程安全。通过这种机制,恶意代码用户也可能获得对象上的锁,并将其永久持有,从而有效地阻塞了其他线程。非恶意用户可以无意中有效地执行相同的操作。

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

private final Object lockObject = new Object();

public void getCount() {
    synchronized( lockObject ) {
        ...
    }
}

Bloch的有效Java(第二版)第70条中提到了此技术。


2
甚至不必恶意,使用从某处获得的对象进行锁定非常简单且天真(例如,因为您要完全同步对该对象的访问)。最好避免此类问题
-Voo

7
@wolfcastle:您能解释一下“代码的恶意用户”是什么吗?通过弄乱自己的应用程序,它们如何变得恶意?
埃里克·罗伯逊

11
@ErickRobertson图像您正在使用公共API提供某种服务。如果您的API公开了一些可变对象,其操作取决于锁定该对象,则一个恶意客户端可以获取该对象,对其进行锁定,然后永远循环并持有该锁定。这可能会阻止其他客户端正常运行。基本上,这是一种拒绝服务类型的攻击。因此,他们不只是搞乱自己的应用程序。
wolfcastle,2014年

尽管这是一个旧答案,但我对此有评论。“恶意用户不可能获得您私人对象上的锁。” -如果“恶意用户”在synchronized块中使用其他对象而不是“您的私人对象”怎么办?
arnobpl

37

区别在于获取锁:

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

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

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

如果仔细选择锁定的对象,则同步块将导致较少的争用,因为不会阻塞整个对象/类。

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


因此,如果我有一个同步块并且其中正在运行一个线程,那么另一个线程可以进入该对象并在该对象的其他位置运行一个同步块吗?如果我有一个同步方法,并且其中有一个线程正在执行,那么没有其他线程可以在该对象中执行,或者只能在对象的同步区域中执行?
邪恶的洗衣机

6

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

  1. 在另一个对象上同步
  2. 限制同步范围

现在,您的特定示例是一个可疑的双重检查锁定模式示例(在较早的Java版本中,该模式已损坏,并且很容易出错)。

如果初始化很便宜,则最好使用final字段而不是在第一个请求上立即进行初始化,这也将消除同步的需要。


5

同步块同步方法之间的区别如下:

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

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


3

同步的,当你想你的类是线程安全的,才应使用。实际上,大多数类无论如何都不应使用同步。同步方法仅在此对象执行期间提供锁定。如果您真的想使您的类线程安全,则应考虑使变量可变同步访问。

使用同步方法的问题之一是该类的所有成员都将使用相同的,这会使您的程序变慢。在您的情况下,同步方法和块将执行相同的操作。我建议使用的是专用锁,并使用类似这样的同步块

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

 public void setX() {
    synchronized(lock) {
        x++;
    }
 }
}

1

在您的情况下,两者是等效的!

同步静态方法等效于相应Class对象上的同步块。

实际上,当您声明同步的静态方法时,将在与Class对象相对应的监视器上获得锁。

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

与...相同

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

好 。如果我同步非静态方法,那么会是什么结果
Sabapathy

然后是按实例或您创建的对象。两个线程可以在彼此独立的两个不同对象中处理同一方法。
Aniket Thakur 2014年


1

不应将其视为最佳使用问题,但它实际上取决于用例或场景。

同步方法

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

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

同步块

要获得一组特定代码块的对象锁,同步块是最合适的。由于块已足够,所以使用同步方法将是浪费。

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


0

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

同步方法:基本上这两种同步方法禁用多线程。因此,一个线程完成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");
    }
}

}

输出量

在方法1中

方法1的最后

方法2 1

方法2 2

方法2 3

方法2 4

方法2 5

方法2 6

方法2 7

方法2 8

方法2 9

方法2的最后


同步块:使多个线程可以同时访问同一对象[启用多线程]。

类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");
        }
    }
}

}

输出量

在方法1中

方法2 1

方法2 2

方法2 3

方法2 4

方法2 5

方法1的最后

方法2 6

方法2 7

方法2 8

方法2 9

方法2的最后

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.