一、单例模式简介
单例模式确保了一个类只有一个对象实例,并为之提供一个全局的访问点,方便对对象实例的管理和调用。
前置知识:
static
关键字:
- 静态变量:在类加载时被初始化,且在整个类的生命周期内保持不变。所有实例共享同一个静态变量。
- 静态方法:属于类而不属于对象,可以直接通过类名调用。静态方法不能访问实例变量和实例方法,只能访问静态变量和静态方法。
- 静态代码块:静在类初始化时执行一次,可以用于静态变量的初始化。
- 静态嵌套类:定义在另一个类中的静态类。它可以访问外部类的所有静态成员,但不能直接访问外部类的实例成员。
类加载时会执行静态代码块、初始化静态变量,但静态方法仅在明确调用时才会执行。
二、单例模式的优点
全局访问点:单例模式确保了一个类只有一个对象实例,并为之提供一个全局的访问点,方便对对象实例的管理和调用。
节省资源:单例模式避免了频繁创建和销毁对象带来的开销,节省了系统资源、提高了性能。
避免重复实例化:单例模式实现的类的对象只会被实例化一次,避免了对象的重复实例化,保证了对象的唯一性。
三、实现方式
饿汉式
优点:在类加载的时候就实例化对象,由于JVM只会加载类一次,保证了线程安全。
缺点:一个类在加载的时候就实例化了对象,有的时候对象并不会被用到,浪费了资源。
class SingleTon{
private static SingleTon instance = new SingleTon();
private SingleTon(){} // 私有化构造器,防止外部访问到
private static SingleTon getInstance(){
return instance;
}
}
懒汉式
主要讲解双重检测锁模式,既实现了在需要的时候创建对象,又满足了线程安全。
class SingleTon{
private static volatile SingleTon instance;
private SingleTon(){} // 私有化构造器,防止外部访问到
private static SingleTon getInstance(){
if(instance == null){ // 过滤,提高性能
synchronized(SingleTon.class){
if(instance == null){ //防止多线程下的线程安全问题
instance = new SingleTon();
}
}
}
return instance;
}
}
要点:
- 对象为什么要加volatile关键字?
防止指令重排带来的影响。
指令重排:为了提高代码的执行效率,编译器和CPU会对代码的执行顺序做一定的优化。在这里instance = new SingleTon();
这一句代码的执行分为以下三个步骤:
①在内存中分配一块空间
②实例化对象
③将该对象的引用指向该空间
但由于存在指令重排,实际的代码的执行顺序可能会从①②③变为①③②。
如果代码执行顺序为①③②,在多线程执行时就可能会发生线程安全问题。
而volatile关键字的作用之一就是避免指令重排。 - 为什么在new 对象的时候要判断两次
if(instance == null)
?
第一次判断:在不加锁的情况下,先判断实例是否为空,如果不为空直接返回,这一步避免了不必要的同步开销,提高了性能,因为只有在第一次创建实例时才需要进入同步块。
第二次判断:多线程环境下,若同时有多个线程通过第一个判断,只有一个线程进入同步代码块,其余线程被阻塞在同步代码块前。此时如果不加第二次的判断,那些通过了第一个判断被阻塞在同步代码块前的线程都会执行同步代码块内的new
操作,创建对象。
静态内部类
class SingleTon{
private SingleTon(){} // 私有化构造器,防止外部访问到
private static class SingleTonHolder{
private static final SingleTon INSTANCE = new SingleTon();
}
private static SingleTon getInstance(){
return SingleTonHolder.INSTANCE;
}
}
静态内部类实现的原理:
- 当首次调用
getInstance()
方法时,SingletonHolder
类会被加载和初始化,静态成员变量INSTANCE
也会被初始化,从而实例化单例对象。 - 由于类加载和初始化过程在JVM中是线程安全的,因此这种方式可以保证线程安全性。
- 由于静态内部类只有在第一次调用
getInstance()
才会被加载且只加载一次,因此这种方式也实现了懒加载的效果,避免了在应用启动时就创建单例对象,节省了系统资源。
枚举方式
class enum SingleTon{
INSTANCE;
// 可选:添加其他的实例变量或方法
// 例如,可以在枚举中定义一个普通的方法
public void doSomething() {
// 方法实现
}
}
实现原理
枚举类介绍
public enum Color {
RED, GREEN, BLUE;
}
在编译后大概是这个样子
public final class Color extends Enum<Color> {
public static final Color RED = new Color("RED", 0);
public static final Color GREEN = new Color("GREEN", 1);
public static final Color BLUE = new Color("BLUE", 2);
private Color(String name, int ordinal) {
super(name, ordinal);
}
// 其他方法和实现...
}
枚举类具体有以下的性质:
-
枚举类本质是一种特殊的类,具有类的一些基本特性和行为,但是编译器对其进行了特殊处理以支持枚举的特性。
-
每个枚举类型都隐式地继承了
java.lang.Enum
类,因此枚举不能显式继承其他类,但可以实现接口。 -
枚举的每个值都是枚举类的实例。比如
Color.RED
是Color
的一个实例。每个枚举实例都是public static final
的。 -
枚举实例是不可变的,一旦创建之后无法改变其状态。这使得枚举在多线程环境中是安全的。
-
枚举类型可以包含静态方法和字段,这些方法和字段对于枚举类来说是通用的。
因此,枚举可以用来实现单例模式。
四、总结
单例模式:确保一个类只有一个实例对象,避免重复创建销毁对象的开销,确保了对象的唯一性。
实现方式:饿汉式(最常用)、懒汉式、静态内部类、枚举(最完美)