单例模式(Singleton Pattern)即一个JVM内存中只存在一个类的实例对象。这种类型的设计模式属于创建型模式。
这种设计模式涉及到单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该对象。
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
- 判断系统是否已经有这个实例,如果有则返回,没有则创建
- 构造函数是私有的
- 没有接口、不能继承
- getInstance()方法需要使用同步锁synchronized防止多线程同时进入造成instance被多次实例化
‘
懒汉式,线程不安全
不支持多线程,因为没有加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
懒汉式,线程安全
能够在多线程中很好的工作,但是效率很低。第一次调用才初始化,即懒加载(lazy loading),避免内存浪费。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton { private static Singleton instance; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
饿汉式
这种方法比较常用,但是容易产生垃圾对象。没有加锁,会提升效率,但类加载时就初始化,浪费内存。
1 2 3 4 5 6 7 8 9
| public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
|
饿汉式变种
1 2 3 4 5 6 7 8 9 10
| public class Singleton { private static Singleton instance = null; static { instance = new Singleton(); } public static Singleton getInstance() { return instance; } }
|
双重检验锁
安全且在多线程情况下保持高性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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; } }
|
静态内部类
能达到双检锁一样的功效,对静态使用延迟初始化,只适应于静态域的情况,双检锁可以在实例域需要初始化时延迟使用
1 2 3 4 5 6 7 8 9 10
| public class Singleton { private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
|
枚举
虽然没有被广泛运用,但是这是实现单例模式的最佳方法,自动支持序列化机制,绝对防止多次实例化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import java.io.*; import java.lang.*;
public enum Singleton { INSTANCE; public void whateverMethod() { System.out.println("hhh"); } } public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException { Singleton.INSTANCE.whateverMethod(); Singleton instance0 = Singleton.INSTANCE; Singleton instance1 = Singleton.INSTANCE; System.out.println("instance0===" + instance0.hashCode()); System.out.println("instance1===" + instance1.hashCode()); Class clazz = Singleton.class; Singleton instance2 = (Singleton) Enum.valueOf(clazz, "INSTANCE"); Singleton instance3 = (Singleton) Enum.valueOf(clazz, "INSTANCE"); System.out.println("instance2===" + instance2.hashCode()); System.out.println("instance3===" + instance3.hashCode()); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("test"))); oos.writeObject(instance0); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("test"))); Singleton instance4 = (Singleton) ois.readObject(); ois.close(); ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream(new File("test"))); Singleton instance5 = (Singleton) ois1.readObject(); ois1.close(); System.out.println("instance4===" + instance4.hashCode()); System.out.println("instance5===" + instance5.hashCode()); }
}
hhh instance0===1927950199 instance1===1927950199 instance2===1927950199 instance3===1927950199 instance4===1927950199 instance5===1927950199
|
注
1.为什么构造函数要使用private
构造器私有,其他类就无法通过new Singleton()
来创建对象实例
2.为什么双重校验锁使用volatile
和两次判空检验
第一个 if 判断是为了减少性能开销
第二个 if 判断是为了避免生成多个对象实例。
volatile为了禁止 JVM 的指令重排,指令重排会导致对象未初始化的情况,造成报错
3.单例模式中唯一实例为什么要用静态
因为getInstance()
是静态方法,而静态方法不能访问非静态成员变量,所以instance
只能使用静态
3.1为什么getInstance()
是静态方法
因为构造器是私有的,程序调用类中方法只有两种方式,
① 创建类的一个对象,用该对象去调用类中方法;
② 使用类名直接调用类中方法,格式“类名.方法名()”;
Singleton instance = Singleton.getInstance();
构造函数私有化后第一种情况就不能用,只能使用第二种方法。
3.2为什么要私有化构造器
目的是禁止其他程序创建该类的对象
如果构造函数不是私有的,每个人都可以通过 new Singleton() 创建类的实例,因此不再是单例。根据定义,对于一个单例,只能存在一个实例。
4.懒汉式与饿汉式区别
懒汉式:先天性线程不安全,当真正需要该实例的时候才去加载,需要我们自己人为上锁控制线程安全问题。
饿汉式:先天性线程安全,当我们项目在启动的时候创建该实例,会导致项目启动比较慢
5.静态内部类与双重校验锁的区别?
静态内部类使用静态关键字去保证我们实例是单例的。
而我们的双重校验锁采用 lock 锁保证安全的。
6.为什么静态内部类写法中,静态类里面获取单例对象要用final修饰
用 final 更多的意义在于提供语法约束。毕竟你是单例,就只有这一个实例,不可能再指向另一个实例。instance有了 final 的约束,后面再有人不小心编写了修改其指向的代码就会报语法错误。
7.单例饿汉式为什么没有线程安全性问题
在getInstance()
获取的实例方法中,没有对资源进行非原子性操作,instance在类加载过程中就被实例化了。即只要不对instance修改,返回的永远是同一个对象,因为instance是private的,所以不需要加锁
破坏单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Test {
public static void main(String[] args) { try { Class<?> clazz = Singleton.class; Constructor c = clazz.getDeclaredConstructor(); c.setAccessible(true); Object o1 = c.newInstance(); Object o2 = c.newInstance(); System.out.println(o1 == o2); } catch (Exception e) { e.printStackTrace(); } } }
|
防止反射破坏单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Singleton {
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
private Singleton() { if(SingletonHolder.INSTANCE != null){ throw new RuntimeException("不允许创建多个实例"); } }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; }
}
|
参考资料:
Java 单例模式从入门到入坟