单例模式
- 通过单例模式的方法创建的类在当前进程中只有一个实例对象。
- 优点:避免频繁创建销毁对象,减轻GC压力。控制全局实例个数:只有一个账号登录。
- 构造单例模式方式:饿汉式、懒汉式、静态内部类、枚举。
单例模式特点
- 私有静态变量:作为类的唯一实例。static 成员在没有创建对象时初始化。静态存储区保存引用指向堆中的唯一对象。
- 私有构造方法:外部类无法通过构造方法创建对象:每次创建对象都需要构造方法,private 限定权限为当前类。
- 公有静态方法:公开获取实例的唯一方法。通过 类.方法 调用。静态方法访问静态实例对象。
- 没有公开的set方法,外部类无法调用set方法创建该实例。
饿汉式、懒汉式
- 创建对象实例的时机不同:饿汉式在类加载时创建对象,懒汉式第一次调用时创建对象。
饿汉式
- 提前把对象new出来,获取类对象的时候已经存在。
- 线程安全,在线程访问单例对象之前就已经创建好了。
- 如果长时间不使用对象,造成资源浪费。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
懒汉式
- 第一次调用getInstance()时创建对象。
- 推荐写法:双重校验锁,线程安全。
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getUniqueInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 懒汉式详细分解过程:
1、无锁写法,非线程安全
- if()条件下可能会创建两个对象。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2、方法加锁,效率太低
- synchronized 对获取对象的静态方法加锁,每次获取实例时先锁起来,再进行判断。缺点:处理速度慢。
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
3、双检,对类加锁,指令重排会出错
- synchronized 对类加锁,调用方法不加锁,创建对象时加锁,防止多线程创建多实例。
- instance = new Singleton() 分为开辟空间,初始化,赋值引用三个指令,初始化和赋值指令可能会重排。
- 指令重排会导致先分配空间赋值再初始化实例,多线程会调用到未初始化的实例。
- 正常指令顺序:new在堆开辟空间,调用构造方法初始化对象,返回空间地址赋值。
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class) {
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
4、双重校验锁,防止指令重排
- 对实例添加 volatile,防指指令重排,对象读写到主存,保证线程可见。
public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类
- 静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
- 外部类加载时,静态内部类不会立即加载,不占内存。
- 外部类调用 getInstance() 方法时加载内部类,延迟实例化。
- 静态内部类利用JVM类的加载机制保证初始化实例对象时线程互斥,线程安全。
- JVM 保证一个类的()方法在多线程环境中被正确地加锁、同步:多个线程同时去初始化一个类,只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
}
枚举
public enum Singleton {
// 定义一个枚举的元素代表实例
Instance;
}
全静态类
- 静态类指 static class 修饰的类,静态类一定是静态内部类。
- 好处:只能通过外部类访问。可以访问外部类的静态属性。
- 把类中所有属性定义为静态也可以实现单例。
- 静态类无需实例化即可使用,效率更高,在编译期就完成了静态绑定。
- 失去面向对象优点:静态方法不能被覆写。但可以继承非 final 的单例类覆写其方法。
- 静态类适用于简单固定、扩展少的场景。
- 单例类最主要的优点:单例模式面向对象的特性更强,可以通过继承来实现多态,通过实现接口提供同一接口的不同实现。
参考
https://blog.csdn.net/jike11231/article/details/106229415