将双重检查锁定从使用 synchronized 转换为锁定 JAVA
Convert double check locking from using synchronized to locks in JAVA
考虑以下代码在 JAVA 8 中使用 synchronized
关键字实现双重检查锁定:
private static void redoHeavyInitialisation() {
if (needToReinitialise()) {
synchronized (MyClass.class) {
if (needToReinitialise()) {
doHeavyInitialisation();
}
}
}
}
使用双重检查锁定的原因是因为初始化很重(因此很懒惰)并且它可能发生不止一次(因此不能使用单例模式,如果我错了请纠正我)。
无论如何,首先,如何将上面的代码转换为使用 JAVA 并发包中的 Lock
而不是使用 synchronized 关键字?
只有在那之后,并且可以随意评论使用 Lock 或 synchronized 关键字哪个更好。
记住,这个问题不是关于 Lock 与 synchronized 的比较。未回答代码转换部分的回答尝试将不会被采纳为已接受的答案。
考虑以下代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HeavyInitializer {
static final Logger logger = LoggerFactory.getLogger(HeavyInitializer.class);
static HeavyInitializer singleton;
public static synchronized HeavyInitializer getInstance() {
if (singleton==null) {
singleton = new HeavyInitializer();
}
return singleton;
}
boolean initialized;
private HeavyInitializer() {
initialized = false;
}
public synchronized void initialize() {
if (!initialized) {
heavyStuffDoneHere();
}
}
public synchronized void reInitilize() {
if (needToReinitialise()) {
heavyStuffDoneHere();
}
}
private void heavyStuffDoneHere() {
initialized = true;
}
private boolean needToReinitialise() {
if (!initialized)
return false;
boolean ret = false;
//Do your check here... and set ret
return ret;
}
}
来自Oracle's doc:
...然后使这些方法同步有两个效果:
首先,对同一个对象的同步方法的两次调用不可能交错。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程将阻塞(暂停执行),直到第一个线程完成该对象。
其次,当同步方法退出时,它会自动与同一对象的任何后续同步方法调用建立 happens-before 关系。这保证了对对象状态的更改对所有线程都是可见的。
尝试使用 Lock 将尝试重新实现同步块。没必要。
使用 ReentrantLock
将同步块转换为等效块非常死记硬背。
首先,您创建一个与您锁定的对象具有相同或相似范围和生命周期的锁。这里你锁定在 MyClass.class
,因此是静态锁,所以你可以将其映射到 MyClass
中的静态锁,例如 MyClass.initLock
.
然后只需替换每个:
synchronized (object) {
和
lock.lock();
try {
和每个关联的右大括号
} finally {
lock.unlock();
}
综合起来你有:
private final static ReentrantLock initLock = new ReentrantLock();
private static void redoHeavyInitialisation() {
if (needToReinitialise()) {
MyClass.initLock.lock();
try {
if (needToReinitialise()) {
doHeavyInitialisation();
}
} finally {
MyClass.initLock.unlock();
}
}
}
Performance-wise 引道之间几乎没有日光。它们本质上具有相同的语义并且通常使用相似的底层机制。过去,存在性能差异 - 有时优化会影响其中一个或另一个,因此在某些 JVM 上您可以找到差异,但是双重检查锁定的 整点 无论如何都是为了避免拿锁,所以只做最简单的事情。当 needToReinitialise()
方法是 运行 时,您只会在非常短的时间内获得锁定,因此锁定成本不会产生任何持续影响。
Singleton Double 检查锁并防止单例对象使用序列化中断。
包pattern.core.java;
导入 java.io.Serializable;
public class Singleton extends Object implements Serializable {
private static final long serialVersionUID = 1L;
private static Singleton sg;
private Singleton() {
}
public static Singleton getSingletonObj() {
if (sg == null) {
synchronized (sg) {
if (sg == null) {
sg = new Singleton();
}
}
}
return sg;
}
/*
* this method ensures that new object will not be created for singleton
* class using serialization and deserialization
*/
protected Object readResolve() {
return sg;
}
/*
* @Override protected Object clone() throws CloneNotSupportedException {
* throw new CloneNotSupportedException(); }
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return sg;
}
}
考虑以下代码在 JAVA 8 中使用 synchronized
关键字实现双重检查锁定:
private static void redoHeavyInitialisation() {
if (needToReinitialise()) {
synchronized (MyClass.class) {
if (needToReinitialise()) {
doHeavyInitialisation();
}
}
}
}
使用双重检查锁定的原因是因为初始化很重(因此很懒惰)并且它可能发生不止一次(因此不能使用单例模式,如果我错了请纠正我)。
无论如何,首先,如何将上面的代码转换为使用 JAVA 并发包中的 Lock
而不是使用 synchronized 关键字?
只有在那之后,并且可以随意评论使用 Lock 或 synchronized 关键字哪个更好。
记住,这个问题不是关于 Lock 与 synchronized 的比较。未回答代码转换部分的回答尝试将不会被采纳为已接受的答案。
考虑以下代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HeavyInitializer {
static final Logger logger = LoggerFactory.getLogger(HeavyInitializer.class);
static HeavyInitializer singleton;
public static synchronized HeavyInitializer getInstance() {
if (singleton==null) {
singleton = new HeavyInitializer();
}
return singleton;
}
boolean initialized;
private HeavyInitializer() {
initialized = false;
}
public synchronized void initialize() {
if (!initialized) {
heavyStuffDoneHere();
}
}
public synchronized void reInitilize() {
if (needToReinitialise()) {
heavyStuffDoneHere();
}
}
private void heavyStuffDoneHere() {
initialized = true;
}
private boolean needToReinitialise() {
if (!initialized)
return false;
boolean ret = false;
//Do your check here... and set ret
return ret;
}
}
来自Oracle's doc:
...然后使这些方法同步有两个效果:
首先,对同一个对象的同步方法的两次调用不可能交错。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程将阻塞(暂停执行),直到第一个线程完成该对象。
其次,当同步方法退出时,它会自动与同一对象的任何后续同步方法调用建立 happens-before 关系。这保证了对对象状态的更改对所有线程都是可见的。
尝试使用 Lock 将尝试重新实现同步块。没必要。
使用 ReentrantLock
将同步块转换为等效块非常死记硬背。
首先,您创建一个与您锁定的对象具有相同或相似范围和生命周期的锁。这里你锁定在 MyClass.class
,因此是静态锁,所以你可以将其映射到 MyClass
中的静态锁,例如 MyClass.initLock
.
然后只需替换每个:
synchronized (object) {
和
lock.lock();
try {
和每个关联的右大括号
} finally {
lock.unlock();
}
综合起来你有:
private final static ReentrantLock initLock = new ReentrantLock();
private static void redoHeavyInitialisation() {
if (needToReinitialise()) {
MyClass.initLock.lock();
try {
if (needToReinitialise()) {
doHeavyInitialisation();
}
} finally {
MyClass.initLock.unlock();
}
}
}
Performance-wise 引道之间几乎没有日光。它们本质上具有相同的语义并且通常使用相似的底层机制。过去,存在性能差异 - 有时优化会影响其中一个或另一个,因此在某些 JVM 上您可以找到差异,但是双重检查锁定的 整点 无论如何都是为了避免拿锁,所以只做最简单的事情。当 needToReinitialise()
方法是 运行 时,您只会在非常短的时间内获得锁定,因此锁定成本不会产生任何持续影响。
Singleton Double 检查锁并防止单例对象使用序列化中断。
包pattern.core.java; 导入 java.io.Serializable;
public class Singleton extends Object implements Serializable {
private static final long serialVersionUID = 1L;
private static Singleton sg;
private Singleton() {
}
public static Singleton getSingletonObj() {
if (sg == null) {
synchronized (sg) {
if (sg == null) {
sg = new Singleton();
}
}
}
return sg;
}
/*
* this method ensures that new object will not be created for singleton
* class using serialization and deserialization
*/
protected Object readResolve() {
return sg;
}
/*
* @Override protected Object clone() throws CloneNotSupportedException {
* throw new CloneNotSupportedException(); }
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return sg;
}
}