单例模式
Singleton(单例模式)
意图:
确保一个类只有一个实例,并提供一个全局访问点来访问该实例
注意事项
- 线程安全:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成实例被多次创建。
- 延迟初始化:实例在第一次调用 getInstance() 方法时创建。
- 序列化和反序列化:重写 readResolve 方法以确保反序列化时不会创建新的实例。
- 反射攻击:在构造函数中添加防护代码,防止通过反射创建新实例。
- 类加载器问题:注意复杂类加载环境可能导致的多个实例问题。
- 结构
单例模式包含以下几个主要角色:
- 单例类:包含单例实例的类,通常将构造函数声明为私有。
- 静态成员变量:用于存储单例实例的静态成员变量。
- 获取实例方法:静态方法,用于获取单例实例。
- 私有构造函数:防止外部直接实例化单例类。
- 线程安全处理:确保在多线程环境下单例实例的创建是安全的。
单例模式的实现
- 单例设计模式分类两种:
- 饿汉式:类加载就会导致该单例实例对象被创建,适用于单线程环境。
- 懒汉式:类加载不会导致该单例实例对象被创建,而是首次使用该对象时才会创建,适用于多线程环境。
饿汉式——静态成员变量
public class Singleton{ private Singleton(){}
private static Singleton instance =new Singleton();
public static Singleton getInstance(){ return instance; } }
|
测试类
public calss Client{ public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); if(instance==instance1){ System.out.println("两个对象是同一个对象"); }else{ System.out.println("两个对象不是同一个对象"); } } }
|
饿汉式——静态代码块
public class Singleton{ private Singleton(){}
private static Singleton instance;
static{ instance = new Singleton(); }
public static Singleton getInstance(){ return instance; } }
|
饿汉式——枚举
- 原由:枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会加载一次。而且枚举类型是所用单例实现中唯一不会被破坏的单例实现模式。
public enum Singleton{ INSTANCE; }
|
测试类
public class Client{ public static void main(String[] args){ Singleton instance = Singleton.INSTANCE; Singleton instance1 = Singleton.INSTANCE; System.out.println(instance==instance1); } }
|
- 总结:
优点: 内存中只有一个实例,减少内存开销,尤其是频繁创建和销毁实例时(如管理学院首页页面缓存)。
避免资源的多重占用(如写文件操作)。
缺点:饿汉式在类加载时就创建了实例对象,可能造成内存的浪费。
懒汉式——线程不安全情况
public class Singleton{ private Singleton(){}
private static Singleton instance;
public static Singleton getInstance(){ if(instance==null){ instance = new Singleton(); } return instance; } }
|
测试类
public class Client{ public static void main(String[] args){ Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance==instance1); } }
|
懒汉式——线程安全情况
public class Singleton{ private Singleton(){}
private static Singleton instance;
public static synchronized Singleton getInstance(){ if(instance==null){ instance = new Singleton(); } return instance; } }
|
懒汉式——双重检查锁
- 优势:解决了单例、性能、线程安全问题。因为在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重新排序操作。可以使用 volatile 关键字修饰成员变量和静态成员变量,防止指令重排序。
public class Singleton{ private Singleton(){}
public static volatile Singleton instance;
public static Singleton getInstance(){ if(instance==null){ synchronized (Singleton.class){ if(instance==null){ instance=new Singleton(); } } } return instance; } }
|
懒汉式——静态内部类
- 原由:静态内部类单里模式由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性、方法被调用时才会被加载,并初始化其静态资源,静态资源由于被static修饰,因此只会被创建一次,并且创建时是线程安全的。
public class Singleton{ private Singleton(){} public static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
|
测试类
public class Client{ public static void main(String[] args){ Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance==instance1); } } 结果为ture
|
- 说明:第一次加载Singleton类时并不会初始化instance,只有第一次调用getInstance()方法时,虚拟机加载SingletonHolder类并初始化instance,并且instance是static的,因此只会被创建一次。这样不仅能保证线程的安全性,还能保证Singleton类的唯一性。
存在的问题——破坏单例模式
破会啊单例模式:使上面定义的单例类可以创建多个对象,美剧方式除外,有两种方式:序列化和反射。
例子:
- 序列化与反序列化
public class Singleton implements Serializable{ private Singleton(){} public static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
|
测试类:
public class Client{ public static void main(String[] args) throws Exception { readObjectFromFile(); readObjectF romFile(); } public static void readObjectFromFile() throw Exception{ ObjectInputStream ois =new ObjectInputStream(new FileInputStream("C:\\Users\\j.txt")); Singleton instance=(Singleton) ois.readObject(); System.out.println(instance); ois.close(); } public static void writeObject2File() throws Exception{ Singleton instance = Singleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\j.txt")); oos.writeObject(instance); oos.close(); } }
|
- 反射
public class Client{ public static void main(String[] args) throws Exception{ Class class =Singleton.class; Constructor constructor = class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton instance1 = (Singleton) constructor.newInstance(); Singleton instance2 = (Singleton) constructor.newInstance(); System.out.println(instance1 == instance2); } }
|
上述问题的解决
1.序列化与反序列化破坏单例模式的解决方法:
- 在Singleton类中添加readResolve()方法,在序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,反之,就返回新new出来的对象。
public class Singleton implements Serializable{ private Singleton(){} public static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } public Object readResolve(){ return SingletonHolder.INSTANCE; } }
|
测试类:public class Client{ public static void main(String[] args) throws Exception { readObjectFromFile(); readObjectF romFile(); } public static void readObjectFromFile() throw Exception{ ObjectInputStream ois =new ObjectInputStream(new FileInputStream("C:\\Users\\j.txt")); Singleton instance=(Singleton) ois.readObject(); System.out.println(instance); ois.close(); } public static void writeObject2File() throws Exception{ Singleton instance = Singleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\j.txt")); oos.writeObject(instance); oos.close(); } }
|
2.反射破坏单例模式的解决方法:public class Singleton { private static boolean flag = false;
private Singleton(){
synchronized (Singleton.class) { if (flag) { throw new RuntimeException("不能创建多个对象"); } flag = true; } }
public static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
|
public class Client{ public static void main(String[] args) throws Exception{ Class class =Singleton.class; Constructor constructor = class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton instance1 = (Singleton) constructor.newInstance(); Singleton instance2 = (Singleton) constructor.newInstance(); System.out.println(instance1 == instance2); } }
|