我使用单例模式的初衷就是保持同一个对象,内部为同一份数据,同时全局均可调用 ~
设计模式分为三种类型,共23种
- 创建型模式:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
- 结构型模式:代理模式、装饰模式、外观模式、享元模式、桥接模式、组合模式、适配器模式
- 行为型模式:观察者模式、策略模式、中介者模式、模版方法模式、命令模式、迭代器模式、职责链模式(责任链模式)、备忘录模式、解释器模式(Interpreter模式)、状态模式、访问者模式
基础了解
单例模式属于创造型模式的一种,在开发中也是最为常用的一种设计模式,其存在实现了数据同步化,同时减少了内存的开支,提升了一定的开发效率~
单例特性
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new 实例 ,主要实现了统一出口的原则,在开发中一定程度上减少了耦合性
适用场景
- 数据需要在全局统一调用、更改等,需要保持数据的一致性
- 对象只允许在独一份,保证唯一性
- 需要频繁的进行创建和销毁的对象(经常用到的对象)
- 创建对象时耗时过多或耗费资源过多(常见于一些工具类对象)
- 频繁被访问的数据库或文件对象 (比如 数据源、session 工厂等)
实战演练
个人使用 双检锁模式
多一点,因为可以既可以保证线程安全性,又能解决掉一些线程并发的场景 ~
饿汉式
核心在于饿
字,体现了饥不择食的状态,不希望等待,所以当使用该实例时 每次都会返回一个新建的实例,内存开销相对交大
优点:写法简单,就是在类装载的时候就完成实例化,避免了线程同步问题
缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading
的效果;不论是频繁调用或从未使用都会造成内存的浪费!
public class Singleton {
public Singleton() {}
public static Singleton singleton = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return singleton;
}
}
懒汉式
总感觉这部分描述有些不太正确,以后若有机会再来优化
核心在于懒
字,意味着 除首次新建实例外,之后若再次调用此实例的话,均是同一实例,减少了内存开销;同时可以保证我们操作的目标为同一目标
在多线程并发的场景下,这种方式并不能保证线程安全
常规而言懒汉式的具体方式其实有三种,主要与synchronized
是否声明、声明位置有关(介于双检索方式的一些不太正确的写法)
- 线程不安全
- 线程安全,同步方法
- 线程安全,同步代码块
在多线程中要注意同步代码块,否则容易在if
的判断期间执行多次实例创建 ~ 有的人会说这样做效率比较低,其实我想说真低不到哪里去 ~ 我认为除非是代码优化到一定高度才在这里进行二次优化
public class Singleton {
private Singleton() {}
private static Singleton singleton;
//静态获取实例,没有就创建,有就返回
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
双检锁
在线程并发中有三大特性:原子性、可见性、有序性
双检锁方式也称双锁检验,我们可以把它看做是基于懒汉式
的优化校验,其中主要涉及到了synchronized
、volatile
关键字
- synchronized 主要作用于有序性,保证同时间尽可能只有一条线程进行操作
- volatile 主要作用于数据的可见性,当有线程更新数据源后及时刷新数据,防止并发造成数据错误
关于 getInstance()
判断条件,首次判断主要查看实例(对象)是否存在,二次判断主要是确保对象的一致性,保证线程安全
public class Singleton {
private Singleton() {}
private static volatile Singleton singleton; //volatile修饰 保证修改的值会立即被更新到主存
public static Singleton getInstance() {
//非空校验,第一把锁
if (singleton == null) {
//线程安全,二次判断(相当于第二把锁)
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类
静态内部类方式区别于以上方式,相比实现更简单,不过这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用
- 类的静态属性只会在第一次加载类(调用
getInstance()
)的时候才会初始化 JVM
特性帮助我们保证了线程的安全性,在类进行初始化(单例对象创建)时,别的线程是无法进入的
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
枚举
简洁清晰,自动支持序列化机制,绝对防止多次实例化(没咋用过,仅用于学习记录
)
public enum Singleton {
INSTANCE;
public void method() {
}
}
思维扩展
以下内容为自我答疑过程,以后或许会移植到一篇新Bolg内
- 一般情况下,饿汉式、懒汉式(包含线程安全和线程不安全俩种方式)都用的比较少;
- 常规在项目中双检锁方式用的更多一些;
- 在要明确实现
lazy loading
效果时,可以考虑静态内部类的实现方式; - 若涉及到反序列化创建对象时,大家也可以尝试使用枚举方式
内部类场景
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
- 内部类可以对同一个包中的其他类隐藏起来
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷
静态域、公有域和实例域的区别
什么是域?在我认为成员变量和局部变量就是域的一种体现,他们因域的不同所以执行效果也不尽相同 ~
静态域 = 公有域
将域定义为static,一个类中只有一个这样的域,
对象首次初始化时将copy一份到堆内,方法区外,作为静态对象 ;而带static修饰的类,方法,字段,代码块都会被copy一份到静态域中,同时独立且唯一地存在于这个静态对象中 ~ 有的人喜欢称其为共享变量 ~
针对于被static修饰的变量、代码块、方法、内部类
变量: 静态变量会copy一份到堆内, 方法区外的静态对象中,那么它属于静态域,可被所有线程共享,一旦成为共享变量后,最好使用原子操作类替代
代码块:静态代码块会copy一份到堆内,方法区外的静态对象中,那么它属于静态域,只会在第一次初始化对象时执行一次,不可访问非静态的部分
方法、内部类: 静态方法或静态内部类会copy一份到堆内, 方法区外的静态对象中,那么它属于静态域,不可访问非静态的部分,但可被所有线程共享
实例域
每一个对象对于所有的实例域都有自己的一份拷贝,所有的非final,非static的对象都存储在实例域中,只可被当前实例查看及更新
域的初始化与赋值
两种情况
- 在建立对象即进行类的实例化时域的初始化
- 在不建立对象,只装载类的时候域的初始化
有两种情况是只装载类而不实例化类
- 用
java classname
执行程序时 - 用
classname.statement
调用类的静态域或静态方法时
赋值方式
- 赋予默认赋值
- 声明变量时同时赋值
- 块赋值(实例块和静态块)
- 构造器赋值
静态内部类和非静态内部类之间得不同
- 内部静态类不需要有指向外部类的引用;但非静态内部类需要持有对外部类的引用
- 非静态内部类能够访问外部类的静态和非静态成员;静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员
- 一个静态内部类不能脱离外部类实体被创建;一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面
懒汉式和饿汉式的区别
相对代码而言有俩点
- 懒汉式多一个if的判空
- 饿汉式不需要if的判空
相比思想而言
- 特点也在其之上的基础,懒汉式首先的初始化,同时之后每次都会通过判空才进行处理,如果其已经存在的话,直接调用已有的那个值
- 饿汉式而言呢,在于每一次去查找实例的时候,直接就是返回一个新的对象。
结果
俩者相比而言,属于本质区别的!懒汉式是创建一次实例一直使用,饿汉式是每次都要开启新的对象,浪费内存~
饿汉式与静态内部类的区别
俩种方式机制类似,但又有不同 ~
- 相同点
都采用了类装载的机制来保证初始化实例时只有一个线程 - 不同点
饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用 ~
静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化 ~
方式分类
此处是看别人文章而来,记录一番
- 枚举 好于 饿汉:占用资源少,不需要延时加载
- 静态内部类 好于 懒汉式:占用资源多,需要延时加载