什么是单例模式
单例模式:可以说是23种设计模式种最简单的模式了,是说类只有一个供全局访问的单个实例,类不能实例化。
这里有两个点:
- 类只有一个供全局访问的实例
- 类不能实例化
为什么使用单例模式
我们期望只提供类的实例供外部系统使用,节约内存。
注意事项:
单例类应该只提供读功能,不提供写功能,也就是说不提供相关的方法改变类实例的状态。
比如:
public class Singleton {
private static Singleton instance = new Singleton();
private int state
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
//使用的地方都可能改变状态,导致内部错乱,不建议使用。改为非单例模式更好。
public void setState(int state) {
this.state = state
}
}
如何使用单例模式
- 我们需要因此构造方法,不让外部构造对象。
- 我们需要提供静态的接口供外部获取单例类的对象。
- 类内部实例化对象
所有的单例模式基本是上面的套路,唯一不同的是类内部实例化对象的过程。
懒汉式 推荐使用
懒汉式是在加载类的class文件时候,直接创建对象,没有使用懒加载,而且是线程安全的。大部分情况下我们都会调用getInstance()方法,而有时不调用的使用,会浪费资源。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
饿汉式 推荐使用
饿汉式是说创建对象的过程中使用懒加载,只有真正调用getInstance()时候才进行对象的创建。而饿汉式又分为线程安全的和线程不安全的,只是getInstace方法上是否通过synchronized加锁,这里强烈推荐直接加锁,因为这样可以避免单例类扩展为多线程运行,而我们又忘记枷锁的情况。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重校验锁 不推荐使用
双重校验锁(DCL,即 double-checked locking)即为在创建对象的过程中添加两次校验,一次是不加锁的校验,一次是加锁的校验。在getInstace需要高并发的使用才会使用,不推荐使用,因为实现起来过于复杂。
这里实现使用注意sychronized和volatile,二者缺一不可
- sychronized保证原子性
- volatile保证有序性
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;
}
}
静态内部类 强烈推荐
此方法是通过classloader来保证单例,而且是懒加载。classloader先加载Singleton类,在调用的getInstance()时候才会去加载SingletonHolder类,从而实现懒加载。强烈推荐使用此方式。
public class Singleton {
private Singleton (){}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举 强烈推荐
public enum Singleton {
INSTANCE;
}
单例模式优缺点
优点:
- 提供统一的全局访问的接口,方便调用,而且支持多线程,保护资源
- 只提供一个类的对象,节约内存
缺点:
- 不适用于内部状态变化的类
- 扩展性差,万一后面扩展为非单例的类,能改死人。
Java中单例的原理
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。-《深入理解Java虚拟机》
所以单例是保证同一个classloader加载同一份Class文件,并且创建唯一的对象,使用不同的classloader加载同一个Class文件,不会是单例的。这也是为什么多进程会导致单例失效的原因。