单例设计模式
单例模式设计一个单一的类,该类创建自己的对象,同时确保只有单个对象创建,这个类提供一种访问其唯一对象的方式,可以直接访问,不需要实例化对象,这种设计模式属于创建型模式。
1.1 单例模式的实现
饿汉式:类加载就会导致该单例实例对象被创建。
懒汉式:类加载不会导致该单例实例对象被创建,而是首次使用该对象时创建
1、饿汉式一:静态变量方式
/**
* 饿汉式:静态成员变量
*/
public class Singleton {
//构造私有方法
private Singleton(){}
//在本类中创建本类对象
private static Singleton instance = new Singleton();
//提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance() {
return instance;
}
}
说明:该方式在成员位置声明Singleton类型的静态变量,并且创建Singleton类的对象instance。instance对象会随着类的加载而创建,会造成内存的浪费。
2、饿汉式二:静态代码块方式
/**
* 饿汉式:静态代码块
*/
public class Singleton {
private Singleton(){}
private static Singleton instance;
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
说明:该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是类加载而创建,也存在内存浪费问题。
3、懒汉式:线程安全方式
/**
* 懒汉式
* 线程安全
*/
public class Singleton {
//创建私有构造
private Singleton(){}
//在本类中创建本类对象
private static Singleton instance;
//提供一个公共方法供外界访问
public static synchronized Singleton getInstance() {//提供锁,保证方法同步执行
if (instance == null){
//如果对象为空则创建对象
instance = new Singleton();
}
return instance;
}
}
说明:在getInstance()
方法上添加了synchronized关键字,解决了线程不安全问题,但是加锁会导致性能变低
4、懒汉式:双重检查锁
/**
* 双重检查锁方式
*/
public class Singleton {
private Singleton(){}
//volatile防止空指针
private static volatile Singleton instance;
//对外提供公共访问方式
public static Singleton getInstance() {
//第一次判断,如果instance的值不为空直接返回对象,如为空进行二次验证
if (instance == null){
synchronized(Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
说明:双重检查锁式解决了单例、性能、线程安全问题,但是在多线程的环境下可能出现空指针问题,出现问题的原因是jvm
在实例化对象的时候对进行优化和指令重排序操作,解决方式是使用volatile
关键字,可以保证可见性和有序性。
5、懒汉模式:静态内部类方式
/**
* 静态内部类方式
*/
public class Singleton {
//私有构造方法
private Singleton(){}
//定义一个静态内部类
private static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供公共访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
说明:第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance
,虚拟机加载SingletonHolder
,并初始化INSTANCE,这样不仅能确保线程安全,也能保证Singleton类的唯一性。
6、枚举方式
/**
* 枚举方式
*/
public enum Singleton {
INSTANCE;
}
说明:枚举类实现单例模式是线程安全的,并且只会加载一次,枚举类型是所有单例实现中唯不会被破坏的,枚举方式属于恶汉式
1.2、存在的问题
破坏单例模式:
-
序列化反序列化
Singleton类:
import java.io.Serializable; public class Singleton implements Serializable { private Singleton(){} private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; }
test类:
import java.io.*; public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException { //向文件中写入一个对象 writeObjectFile(); //从文件中读取对象 Singleton s1 = readObjectFromFile(); Singleton s2 = readObjectFromFile(); //判断是否是同一个对象 System.out.println(s1==s2); } public static void writeObjectFile() throws IOException { //创建输出流对象 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("a.txt")); //创建一个实例对象 Singleton instance = Singleton.getInstance(); //将对象写入文件 outputStream.writeObject(instance); } private static Singleton readObjectFromFile() throws IOException, ClassNotFoundException { //创建输入流对象 ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("a.txt")); //读取第一个对象 Singleton instance = (Singleton) inputStream.readObject(); return instance; }
结果为false,说明序列化和反序列化破坏了单例模式
-
反射
Singleton类:
public class Singleton { private Singleton(){} private static volatile Singleton instance; public static Singleton getInstance() { if (instance != null) { return instance; } synchronized(Singleton.class){ if (instance != null){ return instance; } instance = new Singleton(); return instance; } }
Test类:
public class Test { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //获取Singleton类的字节码对象 Class<Singleton> singletonClass = Singleton.class; //获取Singleton类的私有无参构造方法对象 Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor(); //取消访问检查 constructor.setAccessible(true); //创建Singleton对象 Singleton s1 = constructor.newInstance(); Singleton s2 = constructor.newInstance(); //判断两个singleton是否是同一个对象 System.out.println(s1==s2); }
结论:运行结果为false,反射会破坏单例设计模式
1.3、问题的解决
-
序列化、反序列化破坏单例模式的解决办法
在singleton类中添加
readResolve
方法,在反序列化时被反射调用,如果定义了这个方法,就会返回这个方法的值,如果没有定义,则返回新new出来的对象Singleton类:
public class Singleton implements Serializable { private Singleton(){} private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } /** * 解决单例模式序列化问题 * @return */ private Object readResolve(){ return SingletonHolder.INSTANCE; }