单例模式
单例模式: java中最简单的设计模式之一,这种类型的设计模式述语创建者模式,它提供了一种创建对象的最佳方式
这种模式只涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
特点
- 单例类: 只能创建一个实例的类
- 访问类:使用单例类
实现
两种实现方法
- 饿汉式:类加载就会创建该单例的对象
- 懒汉式:加载类不会导致该实例对象被创建,首次使用该对象时才会创建
饿汉式
Singleton是在类加载的时候创建的对象,饿汉式存在内存浪费问题
静态变量方式
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton {
private Singleton() { } private static Singleton INSTANCE = new Singleton();
public static Singleton getInstance() { return INSTANCE; } }
|
静态代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Singleton { private Singleton() {}
private static Singleton instance;
static{ instance = new Singleton(); } public static Singleton getInstance(){ return instance; } }
|
懒汉式
方式一(双重检查方式锁)
在多线程情况下会出现空指针的情况,出现问题原因是JVM在实例化对象时会进行优化指令重排序操作,解决双重检查锁模式带来的空指针问题,需要加上volatile关键字,可与保证指令可见性和有序性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Singleton { private Singleton() { }
private static volatile Singleton instance;
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.instance) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
|
方式二(静态内部类)
静态内部类单例方式由内部类创建,由于JVM在加载外部类过程中,是不会加载内部类的,只有内部类的属性方法被调用时才会被加载,并且初始化其静态属性,静态属性由于被static修饰,保证是会被实例化一次,并且严格保证实例化顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Singleton { private Singleton() {}
private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
|
静态内部类单例模式是一种优秀的设计模式,是开源项目中比较常用的一种单例模式,在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间浪费
方式三(枚举方式)
枚举类实现单例模式是极力推荐的单例模式,因为枚举类是线程安全的,并且只会加载一次,设计者充分利用枚举这个铽行来实现单例模式,枚举写法十分简单,并且枚举类型是所有单例模式中唯一一个不会被破坏的模式
枚举方式属于饿汉式
1 2 3
| public enum Singleton { INSTANCE; }
|
破坏单例模式
两种方式破坏单例模式
序列化反序列化
序列化可以破坏单例模式,但是枚举类型可以防止单例模式被破坏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Demo04 {
public static void main(String[] args) throws Exception { Singleton singleton1 = getObj(); Singleton singleton2 = getObj(); System.out.println(singleton1 == singleton2); }
public static Singleton getObj() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Users\\xss\\Desktop\\Singleton\\obj.txt")); Singleton singleton = (Singleton) ois.readObject(); ois.close(); return singleton; }
public static void writeObj() throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Users\\xss\\Desktop\\Singleton\\obj.txt")); oos.writeObject(Singleton.getInstance()); oos.close(); } }
|
对枚举类型进行序列化反序列化,得到的还是相同的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Demo04 {
public static void main(String[] args) throws Exception { writeObj(); shejimoshi.singleton.Demo03.Singleton singleton1 = getObj(); shejimoshi.singleton.Demo03.Singleton singleton2 = getObj(); System.out.println(singleton1 == singleton2); }
public static shejimoshi.singleton.Demo03.Singleton getObj() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Users\\xss\\Desktop\\Singleton\\obj.txt")); shejimoshi.singleton.Demo03.Singleton singleton = (shejimoshi.singleton.Demo03.Singleton) ois.readObject(); ois.close(); return singleton; }
public static void writeObj() throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Users\\xss\\Desktop\\Singleton\\obj.txt")); oos.writeObject(shejimoshi.singleton.Demo03.Singleton.INSTANCE); oos.close(); } }
|
暴力反射破坏单例模式
反射会导致单例模式被破坏
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Demo05 { public static void main(String[] args) throws Exception{ Class clazz = Singleton.class; Constructor cons = clazz.getDeclaredConstructor(); cons.setAccessible(true);
Singleton singleton1 = (Singleton) cons.newInstance(); Singleton singleton2 = (Singleton) cons.newInstance();
System.out.println(singleton2 == singleton1); } }
|
解决单例模式被破坏的问题
JDK 单例模式使用
Runtime
Runtime类使用饿汉式实现单例模式
JDK源码分析
1 2 3 4 5 6 7 8 9 10 11 12
| public class Runtime { private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() { return currentRuntime; }
private Runtime() {}
|