单例中的双重检查锁定

这是我的单例模式定制课程。在这段代码中,我使用了如下双重检查锁定模式。我读过很多关于某些资源的文章,他们说双重检查是有用的,因为它可以防止两个并发线程同时运行,从而产生两个不同的对象。

public class DoubleCheckLocking {


public static class SearchBox {
private static volatile SearchBox searchBox;


// private constructor
private SearchBox() {}


// static method to get instance
public static SearchBox getInstance() {
if (searchBox == null) { // first time lock
synchronized (SearchBox.class) {
if (searchBox == null) {  // second time lock
searchBox = new SearchBox();
}
}
}
return searchBox;
}
}

我还是不太懂上面的代码。当 instance 为 null 时,如果两个线程一起运行同一行代码,那么问题是什么?

if (searchBox == null) {
synchronized (SearchBox.class) {
if (searchBox == null) {
searchBox = new SearchBox();
}
}
}

当它出现的时候。两个线程都会看到 object 为 null。然后两者同步。然后是 他们再次检查,仍然看到它为空。并创建两个不同的对象。哎呀。

请给我解释一下,我理解错了什么?

谢谢:)

58672 次浏览

不,因为您正在获取对 SearchBox.class的锁,所以一次只有一个线程将进入同步块。所以第一个线程进入,然后发现 searchBox是空的,并创建它,然后离开同步块,然后第二个线程进入块,然后它发现 searchBox不是空的,因为第一个线程已经创建了它,所以它不会创建一个新的 searchBox实例。

双重检查模式用于避免每次执行代码时获得锁。如果调用没有一起发生,那么第一个条件将失败,代码执行将不执行锁定,从而节省资源。

只有当您担心许多线程同时调用单例时,或者担心获取一个锁的成本时,才需要使用这种双重检查锁。

其目的是防止不必要的同步,从而使您的代码在多线程环境中保持快速。

点击这个链接了解更多信息。

如果您使用 Java 1.5或更高版本,并在双重检查锁定机制中使用 volatile关键字,那么它将工作得很好。因为您使用的是 volatile关键字,所以您的示例不会因为上面的链接而中断。

让我们看看这个代码:

1 if (searchBox == null) {
2     synchronized (SearchBox.class) {
3     if (searchBox == null) {
4         searchBox = new SearchBox();
5     }
6 }

我们来理理头绪。假设我们有两个线程 AB,假设其中至少有一个线程到达第3行,并且观察到 searchBox == nulltrue。由于 synchronized块,两个线程 searchBox == null0同时位于第3行。这是理解双重检查锁定模式为什么起作用的 searchBox == null1。因此,无论是 A还是 B都必须首先通过 synchronized。不失一般性,假设那根线是 abc 0。然后,当看到 searchBox == null为 true 时,它将进入语句的主体,并将 B0设置为 B1的一个新实例。然后它将最终退出 synchronized块。现在轮到 B进入了: 记住,B被阻止等待 A退出。现在,当它进入块,它将观察 B0。但是 A只是将 B0设置为非 B9值而已。成交。

顺便说一下,在 Java 中,实现单例的最佳方法是使用单元素 enum类型。来自 有效的爪哇:

虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方式。

if (searchBox == null) { //1
synchronized (SearchBox.class) {
if (searchBox == null) {  //2
searchBox = new SearchBox();
}
}
}
}
  1. 如果已经创建了一个实例,不要做任何事情——避免锁定线程
  2. 获取锁的第一个线程检查并发现没有这样的对象,然后创建它。它释放锁,第二个也可以做同样的事情——它必须检查对象是否存在,因为第一个可能已经创建了对象。

所以基本上外部 if是用来防止冗余锁-它让所有线程知道已经有一个对象,他们不需要锁定/做任何事情。内部 if用于让并发线程知道另一个线程是否已经创建了对象。