定义:保证一个雷只有一个实例,并且提供一个全局访问点
场景:线程池、数据库连接池等
实现方式:
1、懒汉模式
1)保证线程安全
2)双重检查重排
3)防止指令重排
public class Singleton {
private static Singleton instance;
// 1、私有构造方法
private Singleton(){
}
//2、 提供共有的调用方法
public static Singleton getIntance(){
if(instance==null){
synchronized (Singleton.class) {
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
这个已经加锁了,虽然没有线程安全问题了,但是还是有问题就是JVM在初始化的时候,当执行到【new Singleton();】时,对class使用javap 可以反编译看到,对instance进行赋值的时候一共分为三步,1、分配空间,返回一个指向性该空间的内存引用,2、把内存引用赋值给instance,3、对空间进行初始化。当执行到第二步完成时,还没有初始化空间,有一个线程进来发现instance不为空,直接返回instance。就会存在空的报错。
解决办法:
private volatile static Singleton instance;
就是在定义的时候加上volatile关键字,防止jvm、cpu等对它重排
2、饿汉模式:在类加载阶段完成实例的初始化。通过类加载机制,来保证我们的线程安全。
public class HungrySingleton {
//1、进行实例化
private static HungrySingleton instance = new HungrySingleton();
//2、提供共有的调用方法
public static HungrySingleton getHungrySingleton(){
return instance;
}
//3、私有化构造方法
private HungrySingleton(){
}
}
对于恶汉模式可以使用内部类实现延迟加载
public class HungrySingleton {
// 1、私有化构造方法
private HungrySingleton() {
}
// 3、提供共有的调用方法
public static HungrySingleton getHungrySingleton() {
return InerClass.instance;
}
static class InerClass {
// 2、进行实例化
private static HungrySingleton instance = new HungrySingleton();
}
}
这个看似完美了,但是还是有一个问题就是当用反射的创建的时候,还是可以重新创建一个实例,为了防止反射创建一个新的实例
代码如下
public class HungrySingleton {
// 1、私有化构造方法
private HungrySingleton() {
if(InerClass.instance != null){
throw new RuntimeException("已经创建了一个实例,别想用反射再来一遍");
}
}
// 3、提供共有的调用方法
public static HungrySingleton getHungrySingleton() {
return InerClass.instance;
}
static class InerClass {
// 2、进行实例化
private static HungrySingleton instance = new HungrySingleton();
}
}
这个虽然解决了反射的问题,但是还有一个序列化和反序列化的问题需要解决,反序列化后,修改属性后还是会有问题
修改如下
import java.io.ObjectStreamException;
import java.io.Serializable;
public class HungrySingleton implements Serializable{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
if(instance != null){
throw new RuntimeException("已经创建了一个实例,别想用反射再来一遍");
}
}
public static HungrySingleton getHungrySingleton() {
return instance;
}
Object readResolve() throws ObjectStreamException{
return instance;
}
}
这样就完美解决了关于序列化和反序列化的签名不一致的问题
3、枚举类型
枚举类型中已经解决了反序列化和反射的问题
public enum EnumSingleton {
INSTANCE;
}
枚举类型的就是这么简单明了