我使用单例模式的初衷就是保持同一个对象,内部为同一份数据,同时全局均可调用 ~
设计模式分为三种类型,共23种
- 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
- 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)、访问者模式
基础了解
单例模式属于创造型模式的一种,在开发中也是最为常用的一种设计模式,其存在实现了数据同步化,同时减少了内存的开支,提升了一定的开发效率~
单例特性
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new 实例 ,主要实现了统一出口的原则,在开发中一定程度上减少了耦合性
适用场景
- 需要 频繁的进行创建和销毁的对象
- 创建对象时耗时过多或耗费资源过多
- 经常用到的对象
- 工具类对象
- 频繁访问数据库或文件的对象(比如 数据源、session 工厂等)
实战演练
我个人使用懒汉式、双检锁多一点 ~
懒汉式
核心在于懒字,懒的原由在于除首次新建实例之外,以后若再次调用此实例的话,均是同一实例,减少了内存开销 ~
常规而言懒汉式的具体方式其实有三种,主要与synchronized是否声明、声明位置有关
- 线程不安全
- 线程安全,同步方法
- 线程安全,同步代码块
在多线程中要注意同步代码块,否则容易在if的判断期间执行多次实例创建 ~ 有的人会说这样做效率比较低,其实我想说真低不到哪里去 ~ 我认为除非是代码优化到一定高度才在这里进行二次优化
public class Singleton {
private Singleton() {
}
private static Singleton singleton;
//静态获取实例,没有就创建,有就返回
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
饿汉式
核心在于饿字,当使用该实例时每次都会返回一个新建的实例,内存开销相对要对懒汉模式更大一些 ~
优点:写法简单,就是在类装载的时候就完成实例化,避免了线程同步问题
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果;如果从始至终从未使用过这个实例,则会造成内存的浪费。
public class Singleton {
public Singleton() {
}
public static Singleton singleton = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return singleton;
}
}
双检锁
双检锁也称双锁检验,更多的是基于懒汉式进行二次校验,这里主要采用了synchronized关键字,在一次判断之后进行线程安全的声明,然后在进行二次判断;这样写的方式减少了内存开销,提供了安全线程,执行效率也不错 ~
public class Singleton {
private Singleton() {
}
private static Singleton singleton;
public static Singleton getInstance() {
//非空校验,第一把锁
if (singleton == null) {
//线程安全,二次判断(相当于第二把锁)
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类
静态内部类的方式效果类似双检锁,但实现更简单;不过这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用 ~
类的静态属性只会在第一次加载类的时候初始化,所以在这里,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的实例化 ~
方式分类
此处是看别人文章而来,记录一番
- 枚举 好于 饿汉
占用资源少,不需要延时加载
- 静态内部类 好于 懒汉式
占用资源多,需要延时加载