什么是单例模式
单例模式是为了确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类就是单例类。提供了全局访问的方法。
单例模式有三个要点:
1.某个类只能有一个实例
2.必须自行创建这个实例
3.必须自行向整个系统提供这个实例
单例模式只包含一个角色:
Singleton(单例):在单例类的内部实现只生成一个实例,同时他提供一个静态的getInstance()方法,让客户端可以访问他的唯一实例,为了防止在外部对其实例化,将其构造方法设置为私有。在单例类内部定义一个静态对象,作为外部共享的唯一实例。
单例模式的优缺点
优点
- 单例模式提供了对唯一实例的受控访问,因为单例类封装了他的唯一实例,所以可以严格控制客户怎么以及何时访问他。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源。
缺点
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了单一职责原则。因为单例类既充当了工厂角色,提供了工厂方法,同时又是产品角色,包含一些业务方法,将产品的创建和产品本身的功能融合到一起。
单例模式的适用场景
- 系统只需要一个实例对象,或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户端调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
单例模式案例
单例的不同实现方式
1. 饿汉模式,线程安全
/**
* 类在被加载时就创建了对象
**/
public class Singleton {
private Singleton() {
}
/**
* 本类内部创建对象实例
*/
private final static Singleton INSTANCE = new Singleton();
/**
* 提供一个公有的静态方法,返回实例对象
*
* @return Singleton
*/
public static Singleton getInstance() {
return INSTANCE;
}
}
2. 懒汉模式,线程不安全
/**
* 单例实例在第一次使用时才创建
**/
public class Singleton {
private static Singleton instance;
private Singleton() {
}
/**
* 提供一个静态的公有方法,当使用到该方法时,才去创建 instance
*
* @return Singleton
*/
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. 懒汉模式,线程安全,不推荐使用
/**
* 使用了 synchronized 来保证并发情况下只会创建一个实例
* 不过 synchronized 的锁粒度比较大,对性能影响比较大
**/
public class Singleton {
private static Singleton instance;
private Singleton() {
}
/**
* 提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
*
* @return Singleton
*/
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
4. 懒汉模式,双重检查,线程安全
/**
* 在成员属性上增加了 volatile 关键字,保证多线程时对实例的可见性以及防止指令重排序
**/
public lass Singleton {
private static volatile Singleton instance;
private Singleton() {
}
/**
* 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
*
* @return Singleton
*/
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
5. 懒汉模式,双重检查,线程不安全
public class Singleton {
private Singleton() {
}
private static Singleton instance = null;
// 当执行instance = new Singleton(); 这行代码时,
// CPU会执行如下指令:
// 1.memory = allocate() 分配对象的内存空间
// 2.ctorInstance() 初始化对象
// 3.instance = memory 设置instance指向刚分配的内存
// 单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。
// 指令重排序对单线程没有影响,单线程下CPU可以按照顺序执行以上三个步骤,
// 但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。
// 如果发生了JVM和CPU优化,发生重排序时,可能会按照下面的顺序执行:
// 1.memory = allocate() 分配对象的内存空间
// 3.instance = memory 设置instance指向刚分配的内存
// 2.ctorInstance() 初始化对象
// 假设目前有两个线程A和B同时执行getInstance()方法,
// A线程执行到instance = new Singleton(); B线程刚执行到第一个
// if (instance == null){处,
// 如果按照1.3.2的顺序,假设线程A执行到3.instance = memory
// 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,
// 就会直接return instance;而实际上,线程A还未执行2.ctorInstance()
// 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,
// 这个未初始化的instance对象一旦被线程B使用,就会出现问题。
public static Singleton getInstance() {
if (instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
6. 饿汉模式,静态代码块,线程安全
public class Singleton {
private Singleton() {
}
private static final Singleton instance;
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
7. 饿汉模式,静态内部类,推荐使用,线程安全
/**
* 可以达到和双重检查一样的效果, 只会在调用 getInstance() 方法时才会加载内部 SingletonInstance
*/
public class Singleton {
private Singleton() {
}
/**
* 写一个静态内部类,该类中有一个静态属性 Singleton
*/
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
/**
* 提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
*
* @return Singleton
*/
public static synchronized Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
8. 饿汉模式,枚举,推荐使用,线程安全
public class SingletonExample {
private SingletonExample() {
}
public static SingletonExample getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample singleton;
// JVM保证这个方法绝对只调用一次
Singleton() {
singleton = new SingletonExample();
}
public SingletonExample getInstance() {
return singleton;
}
}
}
单例模式在源码中的应用
Runtime
/**
* 使用的是饿汉模式
**/
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}