单例模式顾名思义就是只实例化一个对象,通过把构造方法私有化来禁止创建实例
饿汉模式的特点是在类加载的时候就创建并初始化一个实例,实例在整个程序运行期间都是唯一的
例如
public class Singleton {
private static Singleton singleton=new Singleton();
private Singleton() {}
public static Singleton getSingleton() {
return singleton;
}
}
注意:
这里的
getSingleton
方法必须是静态方法,因为静态方法可以通过 类名.方法 的方法使用,而非静态方法则要创建实例,这与我们单例模式的规则不相符
懒汉模式的特点是需要的时候在创建实例,实例在整个程序运行期间都是唯一的
例如
public class Singleton {
private static Singleton singleton=null;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton==null) {
singleton=new Singleton();
}
return singleton;
}
}
上面代码在单线程模式下是没问题的,但是在多线程模式下就会存在线程安全问题
如果在首次创建实例,多个线程同时调用getSingleton
方法,就有可能创建多个实例
因此我们可以进行对getSingleton
方法进行加锁
public class Singleton {
private static Singleton singleton=null;
private Singleton() {}
public static synchronized Singleton getSingleton() {
if (singleton==null) {
singleton=new Singleton();
}
return singleton;
}
}
这样虽然解决刚刚线程安全的问题,但每次调用getSingleton
方法都要加锁,增加不少的开销
我们发现上面线程安全问题只存在于首次创建实例的情况,因此我们只需要对singleton
为空的时候单独处理即可
因此我们可以当singleton
为空的时候加锁再判断一次是否为空即可
public class Singleton {
private static Singleton singleton=null;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton==null) {
synchronized (Singleton.class) {
if (singleton==null) {
singleton=new Singleton();
}
}
}
return singleton;
}
}
粗略的看上去好像没什么问题,实际上这里还有一个指令重排序的坑
通过查阅资料知道singleton=new Singleton();
这个代码在执行的时候实际是执行3句伪代码
memory=allocate(); //1.分配对象的内存空间
ctorInstance(memory); //2. 初始化对象
instance=memory; //3. 设置instance指向刚分配的内存地址
JVM在执行的时候可能就会优化成 1 3 2
的顺序执行
可能导致在多线程环境下,还没执行2就已经被其他线程返回了一个刚分配的地址
同样存在线程安全问题,这就需要我们使用volatile
关键字来禁止指令重排序
public class Singleton {
private static volatile Singleton singleton=null;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton==null) {
synchronized (Singleton.class) {
if (singleton==null) {
singleton=new Singleton();
}
}
}
return singleton;
}
}
这样一个线程安全的懒汉模式就完成了