繁体   English   中英

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

[英]Why is synchronized block better than synchronized method?

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

同步方法:

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

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

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

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

这就像你写的一样。

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

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

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

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

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

private final Object lockObject = new Object();

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

Bloch 的 Effective Java(第 2 版)第 70 项中提到了这种技术

不同之处在于获取的是哪个锁:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

在你的情况下,两者都是等价的!

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

实际上当你声明一个synchronized静态方法时,是在Class对象对应的monitor上获得锁的。

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

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

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

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

同步方法

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

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

同步块

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

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

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

同步方法:基本上这两种同步方法禁用多线程。 所以一个线程完成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 中

方法一的最后

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

}

输出

在方法 1 中

方法 2 1

方法 2 2

方法 2 3

方法 2 4

方法 2 5

方法一的最后

方法 2 6

方法 2 7

方法 2 8

方法 2 9

方法二的最后

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM