单例模式
概念:一个类只能有一个实例对象
使用场景:计数器,不用每次刷新都在数据库里加一次,可以用单例先缓存起来
思路:
- 首先创建一个常量
- 构造方法改为私有,类外部不能创建对象
- 通过一个公开的方法,返回这个对象,供外部使用
饿汉式单例
特点:线程安全,但类在装载时就实例化,浪费空间
class Singleton {
private static Singleton _instance = new Singleton();
private Singleton() {}
public static Singleton getInstance(){
return _instance;
}
}
懒汉式单例
特点:使用时创建,线程不安全,加同步后线程安全
class Singleton {
private static Singleton _instance;
private Singleton() {}
public static synchronized Singleton getInstance(){
if(_instance == null){
_instance = new Singleton();
}
return _instance;
}
}
静态内部类的单例(兼具上述两种方式的优点)
class Singleton{
private Singleton(){}
private static class Holder{
static Singleton _instance = new Singleton();
}
public static Singleton getInstance(){
return Holder._instance;
}
}
双检锁/双重校验锁(DCL,即 double-checked locking)
class Singleton {
private volatile static Singleton _instance;
private Singleton() {}
public static Singleton getInstance(){
if(_instance == null){ //提高效率,避免每次都要判断是否获得锁
synchronized(Singleton.class){
if(_instance == null){
_instance = new Singleton();
}
}
}
return _instance;
}
}
使用volatile的原因:
在new Singleton()过程有三个步骤:
- 在堆中开辟空间,分配地址
- 根据类加载的初始化顺序进行初始化
- 将内存地址返回给栈中的引用变量
但是Java内存模型允许无需写入,有些编译器因为性能原因可能会把2和3步进行重排序
当线程A进入并在堆中开辟空间,分配地址后,先将内存地址返回给栈中变量时,线程B此时恰好进入判断实例已经存在后便拿去使用,而此时的实例还没有完成第2步初始化动作,所以线程B拿到的实例是不可使用的。
因此在_instance
前加入关键字volatile
。使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。 但是只在 JDK5 及之后有效。
枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}