对象创建模式之“单例模式Singleton”
动机不纯
- 在软件系统中会有一些特殊的类,需要保证在系统中只有一个实例,以保证他们的逻辑正确性、良好的效率
- 绕过常规的构造器(Client不能直接new),提供一种机制保证一个类只有一个实例(任何地方,不同类之中使用的都是同一个对象指向同一个内存)?
- 责任:类设计者,而非Client使用者。
Singleton模式定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点
模式结构类图
要点
- Singleton模式中的实例构造器可以设置为protected以允许子类派生(但要保证子类也是singleton)
- Single模式一般不要支持拷贝构造函数、Clone接口(工厂模式),因为会导致多个实例对象,与Singleton模式违背
- 多线程下的Singleton模式的线程安全保证(不因为线程抢占问题让两个线程都同了null判断,导致进行了两次new)?--- 双检查锁的正确实现(使用)
最佳实践
单线程
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null ) {
instance = new Singleton();
}
return instance;
}
}
// Singleton s1=Singleton.getInstance();
// Singleton s2=Singleton.getInstance();
// Singleton s3=Singleton.getInstance();
多线程1:同步锁 - 解决线程安全;双重判断 - 解决线程锁的性能问题
public class Singleton {
private static volatile Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null ) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
/*
1. 先分配内存
2. 执行构造器
3. 将内存地址复制给instance
*/
/*
1. 先分配内存
2. 将内存地址复制给instance
3. 执行构造器
*/
多线程2:静态变量的方式解决线程安全问题
public class Singleton {
private Singleton() { }
//lazy load
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
注意拾遗
- 静态对象(变量)存在哪里? static block,只有单线程能进去--->保证线程安全 java程序在内存中的存储分配情况: 堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 栈区: ** 1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中 2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。 3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。 方法区: 1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量**。 2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
- 多线程的情况:线程不安全---> 抢占线程的情况,但被判断的变量为null时,可能两个线程都会通过,对象就会被new两次,破坏了单例模式
- 编译 reorder :使用volatile,保证以下代码编译结果为情况1(java 5.0才加入的volatile关键字机制)
Obj o = new Obj(); 情况1: 1 先分配内存 2 执行构造器 3 将内存地址复制给instance 情况2: 1 先分配内存 2 将内存地址复制给instance 3 执行构造器
- lazy load 懒加载 --- 晚加载:变量到了用到的时候才加载(被调用时) 在Client程序中,只要用到某个类,ClassLoader就会把类中的静态变量都加载到内存中,这样静态变量是无法晚加载的,但是被加载的类的内部类是不会被立刻加载,直到执行到主类中调用到该内部类的代码时才会被加载。 我们可以通过这种机制,实现一些变量的懒加载 --- 创建内部类,把变量放到该内部类中