乐趣区

关于java:java安全编码指南之锁的双重检测

简介

双重检测锁定模式是一种设计模式,咱们通过首次检测锁定条件而不是理论取得锁从而缩小获取锁的开销。

双重查看锁定模式用法通常用于实现执行提早初始化的单例工厂模式。提早初始化推延了成员字段或成员字段援用的对象的结构,直到理论须要才真正的创立。

然而咱们须要十分小心的应用双重检测模式,以防止发送谬误。

单例模式的提早加载

先看一个在单线程失常工作的单例模式:

public class Book {

    private static Book book;

    public static Book getBook(){if(book==null){book = new Book();
        }
        return book;
    }
}

下面的类中定义了一个 getBook 办法来返回一个新的 book 对象,返回对象之前,咱们先判断了 book 是否为空,如果不为空的话就 new 一个 book 对象。

初看起来,如同没什么问题,咱们认真考虑一下:

book=new Book()其实一个简单的命令,并不是原子性操作。它大略能够合成为 1. 分配内存,2. 实例化对象,3. 将对象和内存地址建设关联。

在多线程环境中,因为重排序的影响,咱们可能的到动向不到的后果。

最简略的方法就是加上 synchronized 关键字:

public class Book {

    private static Book book;

    public synchronized static Book getBook(){if(book==null){book = new Book();
        }
        return book;
    }
}

double check 模式

如果要应用 double check 模式该怎么做呢?

public class BookDLC {
    private static BookDLC bookDLC;

    public static BookDLC getBookDLC(){if(bookDLC == null){synchronized (BookDLC.class){if(bookDLC ==null){bookDLC=new BookDLC();
                }
            }
        }
        return bookDLC;
    }
}

咱们先判断 bookDLC 是否为空,如果为空,阐明须要实例化一个新的对象,这时候咱们锁住 BookDLC.class, 而后再进行一次为空判断,如果这次不为空,则进行初始化。

那么上的代码有没有问题呢?

有,bookDLC 尽管是一个 static 变量,然而因为 CPU 缓存的起因,咱们并不可能保障以后线程被赋值之后的 bookDLC,立马对其余线程可见。

所以咱们须要将 bookDLC 定义为 volatile,如下所示:

public class BookDLC {
    private volatile static BookDLC bookDLC;

    public static BookDLC getBookDLC(){if(bookDLC == null){synchronized (BookDLC.class){if(bookDLC ==null){bookDLC=new BookDLC();
                }
            }
        }
        return bookDLC;
    }
}

动态域的实现

public class BookStatic {private static BookStatic bookStatic= new BookStatic();

    public static BookStatic getBookStatic(){return bookStatic;}
}

JVM 在类被加载之后和被线程应用之前,会进行动态初始化,而在这个初始化阶段将会取得一个锁,从而保障在动态初始化阶段内存写入操作将对所有的线程可见。

下面的例子定义了 static 变量,在动态初始化阶段将会被实例化。这种形式叫做提前初始化。

上面咱们再看一个提早初始化占位类的模式:


public class BookStaticLazy {

    private static class BookStaticHolder{private static BookStaticLazy bookStatic= new BookStaticLazy();
    }

    public static BookStaticLazy getBookStatic(){return BookStaticHolder.bookStatic;}
}

下面的类中,只有在调用 getBookStatic 办法的时候才会去初始化类。

ThreadLocal 版本

咱们晓得 ThreadLocal 就是 Thread 的本地变量,它实际上是对 Thread 中的成员变量 ThreadLocal.ThreadLocalMap 的封装。

所有的 ThreadLocal 中寄存的数据实际上都存储在以后线程的成员变量 ThreadLocal.ThreadLocalMap 中。

如果应用 ThreadLocal,咱们能够先判断以后线程的 ThreadLocal 中有没有,没有的话再去创立。

如下所示:

public class BookThreadLocal {
    private static final ThreadLocal<BookThreadLocal> perThreadInstance =
            new ThreadLocal<>();
    private static BookThreadLocal bookThreadLocal;

    public static BookThreadLocal getBook(){if (perThreadInstance.get() == null) {createBook();
        }
        return bookThreadLocal;
    }

    private static synchronized void createBook(){if (bookThreadLocal == null) {bookThreadLocal = new BookThreadLocal();
        }
        perThreadInstance.set(bookThreadLocal);
    }
}

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-double-check-lock/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版