Java 为什么同步块比同步方法更好?
我已经开始学习线程同步 同步方法:Java 为什么同步块比同步方法更好?,java,multithreading,synchronization,Java,Multithreading,Synchronization,我已经开始学习线程同步 同步方法: 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 Sin
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
关键字,它会将整个方法同步到对象或类。在您的情况下,两者是等效的强>
同步静态方法相当于相应类对象上的同步块
public static synchronized int getCount() {
// ...
}
事实上,当您声明同步静态方法锁时,会在与类对象对应的监视器上获得
public static synchronized int getCount() {
// ...
}
与
public int getCount() {
synchronized (ClassName.class) {
// ...
}
}
定义“更好”。同步块更好,因为它允许您:
如果您的初始化成本较低,那么最好立即使用最后一个字段进行初始化,而不是在第一次请求时进行初始化,这样还可以消除同步的需要。仅当您希望您的类是线程安全的时才应使用synchronized。事实上,大多数类无论如何都不应该使用synchronizedsynchronized方法仅在此对象上提供锁,并且仅在其执行期间提供锁。如果你真的想让你的类是线程安全的,你应该考虑让你的变量<强>易失< /强>或<强>同步< /强> Access。< /P> 使用同步方法的一个问题是,类的所有成员都将使用相同的锁,这将使程序变慢。在您的情况下,同步方法和块将执行相同的操作。我建议使用一个专用的锁和一个同步块类似的东西
public class AClass {
private int x;
private final Object lock = new Object(); //it must be final!
public void setX() {
synchronized(lock) {
x++;
}
}
}
因为锁是昂贵的,当您使用同步块时,您只在
\u instance==null
时锁定,并且在\u instance
最终初始化之后,您将永远不会锁定。但是,当您在方法上同步时,即使在初始化\u实例之后,您也会无条件地锁定。这就是双重检查锁定优化模式背后的思想。虽然通常不关心安全问题,但从安全角度来看,最好在私有对象上使用synchronized,而不是将其放在方法上
将它放在方法上意味着您正在使用对象本身的锁来提供线程安全性。通过这种机制,代码的恶意用户也有可能获得对象上的锁,并永久持有它,从而有效地阻止其他线程。非恶意用户可以在无意中有效地做同样的事情
如果使用私有数据成员的锁,则可以防止这种情况,因为恶意用户不可能获得私有对象的锁
private final Object lockObject = new Object();
public void getCount() {
synchronized( lockObject ) {
...
}
}
Bloch的《有效Java》(第二版)第70项中提到了这种技术。不同之处在于获得锁的位置:
- 同步方法获取整个对象的锁。这意味着当方法由一个线程运行时,其他线程不能在整个对象中使用任何同步方法
- synchronized块在synchronized关键字后括号之间的对象中获取锁。这意味着在同步块退出之前,没有其他线程可以获取锁定对象的锁
因此,如果要锁定整个对象,请使用同步方法。如果要使对象的其他部分可供其他线程访问,请使用同步块
如果仔细选择锁定的对象,同步块将导致较少的争用,因为整个对象/类都没有被阻塞
这同样适用于静态方法:同步静态方法将在整个类对象中获取锁,而静态方法中的同步块将在括号之间的对象中获取锁。不应将其视为最佳使用问题,但这实际上取决于用例或场景
同步方法
整个方法可以标记为synchronized,从而在此引用(实例方法)或类(静态方法)上产生隐式锁。这是实现同步的非常方便的机制
步骤
线程访问同步方法。它隐式地获取锁并执行代码。
如果其他线程想要访问上述方法,它必须等待。线程无法获得锁,将被阻塞,必须等待锁释放
同步块
要为一组特定的代码块获取对象锁,同步块是最合适的。由于块已足够,使用同步方法将是一种浪费
更具体地说,对于同步块,可以定义要在其上获取锁的对象引用。同步块与同步方法之间的区别
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");
}
}
}