单例模式
一、何为设计模式
在软件开发过程中,总会遇到各种各样的问题,而设计模式就是众多开发者在实践中总结出来的解决一般性问题的方案。
设计模式可分为三大类,分别是创新型模式(creational)、结构型模式(structural)和行为型模式(behavioral)
二、单例模式
描述:单例模式属于创建型模式,是java中最简单的设计模式之一。在创建单例类时,同时创建自己的对象,再提供访问此对象的唯一方式。使用此对象时,可以直接访问,不需要实例化。保证一个类仅有一个实例,并提供一个访问它的全局访问点。
优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
三、单例模式的几种实现方式
Concept: lazy initialization 就是在使用的时候才进行初始化,与之对应的是在类加载时就初始化(立即加载)。平时所说的延迟加载和立即加载。
多线程同步问题:当多个线程同时操作一个可共享的资源时会出现线程安全问题,将会导致数据不一致,因此使用同步锁来防止该操作执行完之前不许被其他线程执行,从而保证了该变量的唯一性和准确性。
背景:实例化对象很消耗资源
3.1 懒汉式,线程安全
描述:第一次使用对象时才会初始化,避免了内存浪费。但同时加锁会影响效率
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3.2 饿汉式
描述:跟懒汉式正好相反,未加锁且类加载就初始化,基于classloader机制避免多线程的同步问题。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
3.3 双检锁/双重校验锁
描述:DCL,即 double-checked locking,使用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
3.4 登记式/静态内部类
描述:适用于静态类,能够达到与双检锁一样的效果,利用classloader 机制来保证初始化 instance 时只有一个线程。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
3.5 枚举
描述:JDK1.5起,实现单例模式的最佳方法,能避免多线程同步问题,自动支持序列化机制,防止多次实例化。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
注意:一般情况下,不使用懒汉方式,用饿汉方式。只有在要明确实现延迟加载效果时,才会使用登记式。如果涉及到反序列化创建对象时,可以使用枚举式。如果有其他特殊的需求,可以使用双检锁方式。
四、破解单例模式
4.1 反射机制破解单例模式(枚举除外)
public class BreakSingleton{
public static void main(String[] args) throw Exception{
Class clazz = Class.forName("Singleton");
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Singleton s1 = c.newInstance();
Singleton s2 = c.newInstance();
//通过反射,得到的两个不同对象
System.out.println(s1);
System.out.println(s2);
}
}
避免破解的方法:
class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton() {
//在构造器中加个逻辑判断,多次调用抛出异常
if(instance!= null){
throw new RuntimeException()
}
}
public static Singleton getInstance(){
return singleton;
}
}
4.2 反序列化机制破解单例模式(枚举除外)
public class BreakSingleton{
public static void main(String[] args) throws Exception{
//先根据单例模式创建对象(单例模式所以s1,s2是一样的)
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
//将s1写入本地某个路径
FileOutputStream fos=new FileOutputStream("本地某个路径下文件");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
//从本地某个路径读取写入的对象
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("和上面的本地参数路径相同"));
Singleton s3=(Singleton) ois.readObject();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);//s3是一个新对象
}
}
避免破解的方法:
class Singleton implements Serializable{
private static final Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance(){
return singleton;
}
//反序列化定义该方法,则不需要创建新对象
private Object readResolve() throws ObjectStreamException{
return singleton;
}
}
—>>>>资料整理自互联网<<<—
个人博客地址:https://whynw.top/