单例模式-java

单例模式是 Java 中最简单的设计模式之一。

特点:

(一)只能有一个实例
(二)必须自己创建自己的唯一实例
(三)需要给其他对象使用提供这一实例

创建方式:

(1)饿汉式:类加载时就会创建该单例对象。(不使用也会一直占用内存)
(2)懒汉式:类加载不会创建对象,首次使用该对象时才会创建。(需注意控制线程安全问题)

具体实现单例模式代码:

(1-1)饿汉式:静态成员变量

public class Singleton {
    //构造方法私有化
    private Singleton() {
    }
    private static Singleton instance = new Singleton();
    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;
    }
}

(1-3)饿汉式:枚举

public enum  Singleton {
    INSTANCE;
}

饿汉式三种创建方法总结:

1、静态成员变量、静态代码块两种方式没有太大区别,在类加载时会执行创建对象方法。
2、枚举是推荐的实现方式,枚举类型是线程安全的,只会装载一次,枚举是所有单例实现中唯一一种不会被破坏的单例实现模式。(下面会介绍破坏单例的方式和解决方法)

(2-1)懒汉式:(线程不安全)

public class Singleton {
    private Singleton() {
    }
    private static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            //线程1执行到此处开始等待
            // 线程2获取到cpu执行权,也会执行到此处
            //多个线程执行到此处,进而会创建多个对象
            instance = new Singleton();
        }
        return instance;
    }
}

(2-2)懒汉式:(同步方法锁,线程安全)

public class Singleton {
    private Singleton() {
    }
    private static Singleton instance;
    //单例绝大部分都是读操作,读操作是线程安全的
    //没必要每个线程必须持有锁才能调用该方法,性能低下
    //调整锁的时机,所以有了新的实现模式,双重检查锁(2-3)方式  
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

(2-3)懒汉式:(双重检查锁,线程安全)

public class Singleton {
    private Singleton() {
    }
    //多线程情况下,可能会出现空指针问题
    //原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
    //在变量属性前用volatile关键字修饰,可以保证可见性和有序性
    private static volatile Singleton instance;
    public static Singleton getInstance() {
        //第一次判断 如果!=null 不需要抢占锁 直接返回对象 进而提升效率
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

(2-4)懒汉式:(静态内部类,线程安全)

/**
*由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化静态属性
*/
public class Singleton {
    private Singleton() {
    }
    private static class SingletonHolder {
        //final修饰 防止外部类对它进行修改
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

懒汉式三种创建方法总结:

1、当不涉及多线程调用时,可以使用(2-1)不加同步锁的方式进行创建
2、当多线程操作时,可以使用(2-2)、(2-3)的实现方式。推荐使用(2-3)双重检查锁的方式,更加高效。
3、更加推荐的是(2-4)静态内部类的实现方式,它是一种优秀的单例模式, 是开源项目中比较常用的单例模式。不需要使用任何锁,也保证了多线程安全,并且不影响性能,也不浪费空间。

破坏单例模式

可以理解为通过一定的方式,使单例类可以创建多个对象,(枚举方式除外)。有两种方式,分别是反射序列化

1、反射

首先我们看一下通过反射创建实例的代码

 //获取字节码对象
 Class clazz = Singleton.class;
 //获取无参的构造方法
 Constructor cons = clazz.getDeclaredConstructor();
 //取消访问检查
 cons.setAccessible(true);
 //创建Singleton对象
 Singleton instance1 = (Singleton) cons.newInstance();
 Singleton instance2 = (Singleton) cons.newInstance();

通过打印instance1instance2地址可以得知这是两个不同的对象。那我可以在无参私有的构造方法中进行处理,在类中添加一个静态变量进行标记是否是第一调用构造方法,在第二次调用时我们抛异常处理。如下代码:(双重检查锁举例)

public class Singleton {
    private static boolean flag;
    private Singleton() {
        if (flag) {
            throw new RuntimeException("不允许创建多个对象");
        }
        flag = true;
    }
    private static volatile Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2、序列化

这里我们以静态内部类的单例模式进行举例

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;
    }
    //当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回,解决序列化反序列化单例被破坏问题
  //  public Object readResolve(){
  //      return SingletonHolder.INSTANCE;
  // }
}
public static  void writeObjToFile() throws Exception {
    //将对象序列化之后 写入到文件中
    Singleton singleton = Singleton.getInstance();
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("***/a.txt"));
    oos.writeObject(singleton);
    oos.close();
}
public static void readObjFromFile() throws Exception{
    //从文件中读取对象
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("***/a.txt"));
    Singleton singleton = (Singleton) ois.readObject();
    System.out.println(singleton);
    ois.close();
}

首先我们的单例需实现Serializable接口,然后再调用writeObjToFile方法将单例Singleton对象写入到文件中。然后再多次调用readObjFromFile读取对象,经过打印对象地址发现每次取出的对象不是同一个,所以该单例被破坏
原因在ObjectInputStream 的readObject()方法,这里jdk源码以截图形式说明:(由于截图不全,大家可以根据代码行数自行查看)

image.png
image.png
image.png

从这几个主要方法中可以得知,我们需要在Singleton类的readResolve方法中,将我们实例返回即可,也就是我上面代码中注释掉的readResolve方法。这时我们再进行读写操作时,就会得到同一个实例。

总结

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容