系列传送门
设计模式一、单例模式
设计模式二、简单工厂模式
设计模式三、工厂模式
设计模式四、抽象工厂模式
简单单例(推荐)
public class SimpleSingleton {
// TODO 第1步:把构造函数私有化,禁止外部使用构造函数创建实例对象
private SimpleSingleton() {
}
// TODO 第2步:定义并初始化私有静态实例变量,在类加载阶段即完成该静态实例的初始化
private static SimpleSingleton instance = new SimpleSingleton();
// TODO 第3步:对外提供一个获取私有静态实例方法
public static SimpleSingleton getInstance() {
return instance;
}
}
基于类加载过程进行说明
- 加载
加载SimpleSingleton.class字节码文件到JVM,生成一个SimpleSingleton的java.lang.Class对象(生成在方法区)。- 验证
验证SimpleSingleton.class字节码中包含的信息是否符合JVN规范,是否会危害JVM自身的安全。- 准备
为类变量(这里是instance)分配内存(分配在方法区),并设置初始值(这里给instance赋值为null)。- 解析
把.class字节码文件里常量池中定义的符号引用(CONSTANT_***格式的字面量)转换为直接引用,即把常量直接指向值所在的地址。- 初始化
执行类构造器<client>方法,完成类变量(这里是instance)的赋值操作(这里是在堆内存上创建SimpleSingleton实例)和执行类静态块(这里没有类静态块)。
如果没有类变量和类静态块,JVM不会生成类构造器<client>方法。- 使用
- 卸载
优点
线程安全。
在JVM启动时的类加载阶段即完成静态实例变量的初始化,不存在多线程争抢创建静态实例的情况。
缺点
对于大量不需要马上使用的单例对象,会在类加载阶段创建大量的实例,占用内存空间。
推荐
如果能确保这个单例一定有使用的机会,也就是说这个单例迟早都要初始化,那么用这个方式也是最简单最直接最好用的。
不安全的延迟初始化单例(不推荐)
public class UnsafeLazySingleton {
// TODO 第1步:把构造函数私有化,禁止外部使用构造函数创建实例对象
private UnsafeLazySingleton() {
}
// TODO 第2步:定义私有静态实例变量,这里不初始化
private static UnsafeLazySingleton instance;
// TODO 第3步:对外提供一个获取私有静态实例方法
public static UnsafeLazySingleton getInstance() {
// TODO 第4步:在这里初始化私有静态实例变量
if (instance == null) {
instance = new UnsafeLazySingleton();
}
return instance;
}
}
基于类加载过程进行说明
- 加载
加载UnsafeLazySingleton.class字节码文件到JVM,生成一个UnsafeLazySingleton的java.lang.Class对象(生成在方法区)。- 验证
验证UnsafeLazySingleton.class字节码中包含的信息是否符合JVN规范,是否会危害JVM自身的安全。- 准备
为类变量(这里是instance)分配内存(分配在方法区),并设置初始值(这里给instance赋值为null)。- 解析
把.class字节码文件里常量池中定义的符号引用(CONSTANT_***格式的字面量)转换为直接引用,即把常量直接指向值所在的地址。- 初始化
执行类构造器<client>方法,完成类变量(这里是instance)的赋值操作(这里没有对静态变量赋值,所以不会初始化instance)和执行类静态块(这里没有类静态块)。
如果没有类变量和类静态块,JVM不会生成类构造器<client>方法。- 使用
线程调用getInstance()方法,发现instance为null,此时会初始化instance静态变量(这里是在堆内存上创建UnsafeLazySingleton实例)。- 卸载
优点
只在第一次使用单例实例时,才初始化单例实例,节省内存空间。
缺点
线程不安全。
多线程情况下,会出现争抢初始化单例实例的情况,最坏情况是每个线程都创建了一次instance实例,造成每个线程持有的instance实例不一致。
不推荐
安全的延迟初始化单例(不推荐)
public class SafeLazySingleton {
// TODO 第1步:把构造函数私有化,禁止外部使用构造函数创建实例对象
private SafeLazySingleton() {
}
// TODO 第2步:定义私有静态实例变量,这里不初始化
private static SafeLazySingleton instance;
// TODO 第3步:对外提供一个获取私有静态实例方法,该接口使用synchronized关键字,保证多线程并发情况下只有一个线程能执行该方法
public synchronized static SafeLazySingleton getInstance() {
// TODO 第4步:在这里初始化私有静态实例变量
if (instance == null) {
instance = new SafeLazySingleton();
}
return instance;
}
}
基于类加载过程进行说明
- 加载
加载SafeLazySingleton.class字节码文件到JVM,生成一个SafeLazySingleton的java.lang.Class对象(生成在方法区)。- 验证
验证SafeLazySingleton.class字节码中包含的信息是否符合JVN规范,是否会危害JVM自身的安全。- 准备
为类变量(这里是instance)分配内存(分配在方法区),并设置初始值(这里给instance赋值为null)。- 解析
把.class字节码文件里常量池中定义的符号引用(CONSTANT_***格式的字面量)转换为直接引用,即把常量直接指向值所在的地址。- 初始化
执行类构造器<client>方法,完成类变量(这里是instance)的赋值操作(这里没有对静态变量赋值,所以不会初始化instance)和执行类静态块(这里没有类静态块)。
如果没有类变量和类静态块,JVM不会生成类构造器<client>方法。- 使用
线程调用getInstance()方法时,先获取当前类的java.lang.Class实例的锁。
如果拿到了锁,执行到getInstance()方法内部,发现instance为null,此时会初始化instance静态变量(这里是在堆内存上创建SafeLazySingleton实例)。
如果没拿到锁,则阻塞等待。- 卸载
优点1
只在第一次使用单例实例时,才初始化单例实例,节省内存空间。
优点2
线程安全。
多线程情况下,只有一个线程能执行getInstance()静态方法。
这里是锁getInstance()静态方法,加锁的对象是当前类的java.lang.Class实例。
缺点
并发情况下,多个线程同时调用getInstance()静态方法时会争抢锁,一次只有一个线程能获得锁,其他线程只能阻塞等待。
每次调用getInstance()静态方法时都要获得锁,执行效率太低
不推荐
双重检测的安全的延迟初始化单例(不怎么推荐)
public class DoubleCheckSafeLazySingleton {
// TODO 第1步:把构造函数私有化,禁止外部使用构造函数创建实例对象
private DoubleCheckSafeLazySingleton() {
}
// TODO 第2步:定义私有静态实例变量,这里不初始化
private static DoubleCheckSafeLazySingleton instance;
// TODO 第3步:对外提供一个获取私有静态实例方法
public static DoubleCheckSafeLazySingleton getInstance() {
// TODO 第4步:在这里检测私有静态实例变量是否已被初始化
if (instance == null) {
// TODO 第5步:还未被初始化,则先给当前类的java.lang.Class实例加锁
synchronized (DoubleCheckSafeLazySingleton.class) {
// TODO 第6步:获得锁之后,在这里再次检测私有静态实例变量是否已被初始化
if (instance == null) {
// TODO 第7步:还未被初始化,则在这里初始化私有静态实例变量
instance = new DoubleCheckSafeLazySingleton();
}
}
}
return instance;
}
}
基于类加载过程进行说明
- 加载
加载DoubleCheckSafeLazySingleton.class字节码文件到JVM,生成一个DoubleCheckSafeLazySingleton的java.lang.Class对象(生成在方法区)。- 验证
验证DoubleCheckSafeLazySingleton.class字节码中包含的信息是否符合JVN规范,是否会危害JVM自身的安全。- 准备
为类变量(这里是instance)分配内存(分配在方法区),并设置初始值(这里给instance赋值为null)。- 解析
把.class字节码文件里常量池中定义的符号引用(CONSTANT_***格式的字面量)转换为直接引用,即把常量直接指向值所在的地址。- 初始化
执行类构造器<client>方法,完成类变量(这里是instance)的赋值操作(这里没有对静态变量赋值,所以不会初始化instance)和执行类静态块(这里没有类静态块)。
如果没有类变量和类静态块,JVM不会生成类构造器<client>方法。- 使用
当前线程调用getInstance()方法,先判断instance是否已被初始化。
如果instance已被初始化,则无需获得锁,直接返回instance
如果instance还未被初始化,则先获得当前类的java.lang.Class实例的锁。
如果没有拿到锁,则当前线程阻塞等待。
当前线程拿到锁之后,执行到getInstance()方法内部。
发现instance已被初始化,直接跳出同步块。
发现instance为null,此时会初始化instance静态变量(这里是在堆内存上创建DoubleCheckSafeLazySingleton实例)。- 卸载
优点1
只在第一次使用单例实例时,才初始化单例实例,节省内存空间。
优点2
线程安全。
多线程情况下,多线程情况下,只有一个线程能获得当前类的java.lang.Class实例的锁并检测和执行初始化单例实例操作。
这里是锁getInstance()静态方法内部的代码块,加锁的对象是当前类的java.lang.Class实例。
当私有静态实例已被初始化时,所有线程将不必去获得锁。
只有当私有静态实例未被初始化时,才会去获得锁,极大的提高了执行效率。
既保证了单例实例的延迟初始化,又兼顾了线程安全。
缺点
这个方式的写法比较繁琐,如果这个单例迟早都要使用,还不如使用SimpleSingleton的方式。
不怎么推荐
网友评论