【设计模式】常用的几种设计模式——单例模式
单例模式
- 0. 单例模式的概念
- 1. 饿汉模式的实现方法(3种)
- 1.1 最常见的实现方式:
- 1.2 利用静态代码块来实现:
- 1.3 枚举方式
- 2. 懒汉模式的实现方法(2种)
- 2.1 双重if的方法:
- 2.2 静态内部类的方法:
- 3. 单例模式存在的问题
- 3.1 序列化和反序列化破坏单例模式
- 3.2 解决序列化和反序列化破坏单例模式
- 3.3 反射破坏单例模式
- 3.4 解决反射破坏单例模式
0. 单例模式的概念
单例模式(Singleton Pattern)是Java中最简单的设计模式,该模式的基本思想就是:某个类负责创建自己的对象,同时确保该类的实例对象只有一个,该类提供唯一一种访问其对象的方式,可以直接访问,不需要实例化其对象。
关键字提取:
- 负责创建自己的对象(即私有化构造方法)
- 实例对象只有一个
- 不需要实例化其对象(即通过静态方法提供接口)
单例模式分为两种:
- 饿汉模式:在类加载时期就已经创建好了对象,这种创建方式相比于懒汉模式更浪费空间
- 懒汉模式:在类加载时期不会创建对象,只有第一次使用时才会创建对象
1. 饿汉模式的实现方法(3种)
1.1 最常见的实现方式:
public class Singleton {
//1.将构造方法私有化
private Singleton(){}
private static Singleton instance = new Singleton();
//2.提供获取对象的静态方法接口
public static Singleton getInstance(){
return instance;
}
}
1.2 利用静态代码块来实现:
/**
* 恶汉式
* 在静态代码块中创建该类对象
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
static {
instance = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
该方法是在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。
1.3 枚举方式
/**
* 枚举方式
*/
public enum Singleton {
INSTANCE;
}
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式
2. 懒汉模式的实现方法(2种)
2.1 双重if的方法:
public class Singleton {
//1.将构造方法私有化
private Singleton(){}
private static volatile Singleton instance;
//2.提供获取对象的静态方法接口
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
- 双重if:假设来了一批线程都调用getInstance(),那他们都会突破第一层的if,在进入第二层if之前,只有一个线程能抢占到锁并创建对象,其他的线程后面依次抢占到锁之后,并不能进入第二层if。此时如果还有别的线程调用getInstance(),他们就会被第一层的if拒绝。
- volatile 修饰instance,是为了保证可见性和有序性,防止JVM在实例化对象的时候进行优化和指令重排序。
2.2 静态内部类的方法:
public class Singleton {
//1.将构造方法私有化
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
//2.提供获取对象的静态方法接口
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性
3. 单例模式存在的问题
3.1 序列化和反序列化破坏单例模式
实现Serializable 接口
public class Singleton implements Serializable {
//1.将构造方法私有化
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
//2.提供获取对象的静态方法接口
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
测试:
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//往文件里面写对象
//writeObjectFile();
//从文件里面读取对象
Singleton s1 = readObjectFromFile();
Singleton s2 = readObjectFromFile();
System.out.println(s1 == s2);
}
private static Singleton readObjectFromFile() throws IOException, ClassNotFoundException {
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\work\\test.txt"));
//第一个读取Singleton对象
Singleton instance = (Singleton) ois.readObject();
return instance;
}
public static void writeObjectFile() throws IOException {
//获取Singleton对象
Singleton instance = Singleton.getInstance();
//创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\work\\test.txt"));
//将instance对象写入文件中
oos.writeObject(instance);
}
}
测试结果:
这就说明序列化和反序列化破坏了单例模式,具体的解决方案见后面。
3.2 解决序列化和反序列化破坏单例模式
在Singleton类中加上readResolve方法:
public class Singleton implements Serializable {
//1.将构造方法私有化
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
//2.提供获取对象的静态方法接口
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
/**
* 下面是为了解决序列化反序列化破解单例模式
*/
private Object readResolve(){
return SingletonHolder.INSTANCE;
}
}
原理:通过查看源码发现在readObject()时,会判断是否有readResolve()方法,如果有就会自动执行该方法,如果没有就创建一个新的对象。
3.3 反射破坏单例模式
public class Singleton {
//1.将构造方法私有化
private Singleton(){}
private static volatile Singleton instance;
//2.提供获取对象的静态方法接口
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
反射创建对象:
public class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取Singleton字节码对象
Class clazz = Singleton.class;
//获取Singleton类私有的无参构造方法
Constructor constructor = clazz.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
//创建Singleton类对象
Singleton s1 = (Singleton)constructor.newInstance();
Singleton s2 = (Singleton)constructor.newInstance();
System.out.println(s1==s2);
}
}
测试结果:
这就说明反射破环了单例模式,解决方案如下。
3.4 解决反射破坏单例模式
因为反射是通过调用私有的构造方法来创建对象,所以为例保证安全,我们就可以在构造方法上面做学问:
public class Singleton {
private static boolean flag = false;
//1.将构造方法私有化
private Singleton(){
//加上这个if可以使效率更高,不用每次都去抢占锁
// if(flag){
// throw new RuntimeException("不能创建多个对象");
// }
synchronized (Singleton.class){
if(flag){
throw new RuntimeException("不能创建多个对象");
}
flag=true;
}
}
private static volatile Singleton instance;
//2.提供获取对象的静态方法接口
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
做了这个处理之后,创建多个对象时,就会报如下异常: