设计模式[1]-单例模式
单例模式
单例模式(Singleton)是一种创建型设计模式,能够保证一个类只有一个实例,并提供了访问该实例的全局节点。
单例的实现步骤有以下步骤,首先将默认构造函数设置为私有,然后创建一个静态方法来调用私有构造函数来创建对象。
单例类型主要分为懒汉模式和饿汉模式。
- 饿汉:类加载就会导致单例对象被创建。
- 懒汉:类加载不会导致单例对象被创建,首次使用该对象时才会创建。
1. 饿汉模式
1.1 静态变量生成
public class HungrySingleton1 {
// 1. 类中直接创建本类实例
private static HungrySingleton1 singleton = new HungrySingleton1();
// 2. 私有构造方法
private HungrySingleton1() {
}
// 3. 公共静态方法,供外部访问
public static HungrySingleton1 getInstance() {
return singleton;
}
}
单元测试通过:
@Test
void getInstance() {
HungrySingleton1 instance1 = HungrySingleton1.getInstance();
HungrySingleton1 instance2 = HungrySingleton1.getInstance();
Assertions.assertEquals(instance1, instance2);
}
1.2 静态代码块生成
public class HungrySingleton2 {
// 1. 声明静态私有变量,在静态代码块中创建本类实例
private static HungrySingleton2 singleton;
static {
singleton = new HungrySingleton2();
}
// 2. 私有构造方法
private HungrySingleton2() {
}
// 3. 公共静态方法,供外部访问
public static HungrySingleton2 getInstance() {
return singleton;
}
}
单元测试通过:
@Test
void getInstance() {
HungrySingleton2 instance1 = HungrySingleton2.getInstance();
HungrySingleton2 instance2 = HungrySingleton2.getInstance();
Assertions.assertEquals(instance1, instance2);
}
饿汉模式是类加载就会创建对象,所以会造成内存浪费。优点是代码简单,而且不用考虑多线程问题。
2. 懒汉模式
2.1 线程不安全
public class LazySingleton1 {
// 1. 声明静态私有变量
private static LazySingleton1 singleton;
// 2. 私有构造方法
private LazySingleton1() {
}
// 3. 公共静态方法,供外部访问,在此处创建对象
public static LazySingleton1 getInstance() {
if (singleton == null) {
singleton = new LazySingleton1();
}
return singleton;
}
}
在getInstance方法中,先判断变量是否存在,不存在的话,就创建对象并返回。但是这种方法是线程不安全的,所以为了保证线程安全,可以给getInstance方法加锁,即2.2的创建方法。
2.2 线程安全,方法加锁
public class LazySingleton2 {
// 1. 声明静态私有变量
private static LazySingleton2 singleton;
// 2. 私有构造方法
private LazySingleton2() {
}
// 3. 公共静态方法,供外部访问,在此处创建对象
public static synchronized LazySingleton2 getInstance() {
if (singleton == null) {
singleton = new LazySingleton2();
}
return singleton;
}
}
跟2.1的代码,主要只有一行有所区别。
public static synchronized LazySingleton2 getInstance()
2.3 线程安全,双重检查锁校验
public class LazySingleton3 {
// 1. 声明静态私有变量
private static volatile LazySingleton3 singleton;
// 2. 私有构造方法
private LazySingleton3() {
}
// 3. 公共静态方法,供外部访问,在此处创建对象
public static LazySingleton3 getInstance() {
// 第一次判断,实例不为空,直接返回
if (singleton == null) {
synchronized (LazySingleton3.class){
// 第二次判断
if(singleton == null){
singleton = new LazySingleton3();
}
}
}
return singleton;
}
}
2.2的创建方式中,锁直接加在getInstance方法,每次调用此方法都会加锁,过于笨重。
所以改进方式是,利用两次判断,第一次判断,如果实例存在,就返回,那如果不存在,就是得加锁,而第二次判断保证了创建的是单一实例。
仔细观察2.3的代码思路,其实是2.1和2.2的结合。
注意:
singleton 变量使用volatile关键字修饰,是因为多线程情况下,有可能出现空指针异常,因为jvm实例化对象的时候会进行指令优化和指令重排序,加上volatile关键字可以保证可见性和有序性。
2.4 静态内部类实现
public class LazySingleton4 {
// 1. 私有构造方法
private LazySingleton4() {
}
// 2. 声明静态内部类
private static class InnerSingleton {
// 3. 内部类中创建外部类的常量对象
private static final LazySingleton4 SINGLETON = new LazySingleton4();
}
// 4. 公共静态方法,供外部访问,返回静态内部类中创建的常量对象
public static LazySingleton4 getInstance() {
return InnerSingleton.SINGLETON;
}
}
JVM加载外部类的时候,不会加载静态内部类,只有在调用内部类的属性或方法的时候才会加载,而且静态内部类中的属性用static修饰,保证了单例,所以这种方法是线程安全且没有造成性能影响和空间的浪费。