单例模式
- 一 官方定义
- 二 单例模式八种方式
- 2.1 饿汉式(静态常量)
- 代码案例
- 案例分析
- 2.2 饿汉式(静态代码块)
- 代码案例
- 案例分析
- 2.3 懒汉式(线程不安全)
- 代码案例
- 案例分析
- 2.4 懒汉式(线程安全,同步方法)
- 代码案例
- 案例分析
- 2.5 懒汉式(线程不安全,同步代码块)
- 代码案例
- 案例分析
- 2.6 双重检查 (推荐使用)
- 代码案例
- 案例分析
- 优点
- 可能出现的问题
- 扩展 - Volatile
- 2.7 静态内部类 (推荐使用)
- 代码案例
- 案例分析
- 2.8 枚举方式
- 代码案例
- 案例分析
- 三 注意事项
- 四 单例模式的使用场景
一 官方定义
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
Spring 中的 bean 默认都是单例模式,每个bean定义只生成一个对象实例,每次 getBean请求获得的都是此实例
二 单例模式八种方式
单例模式的八种实现方式,如下所示
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举方式
2.1 饿汉式(静态常量)
代码案例
class Singleton {
//一:构造器的私有化 防止外部用构造器...
private Singleton() {
}
//二:类的内部创建对象 final static
private static final Singleton singleton = new Singleton();
//三:对外提供公共的静态方法 返回该类唯一的对象实例
public static Singleton getInstance() {
return singleton;
}
}
案例分析
//案例演示 - 饿汉式
public class SingletonDemo {
public static void main(String[] args) {
// 方式一:静态常量
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1); //true
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
通过结果可以发现返回的是同一个对象所以单例模式是实现的。
写法分析
优势
: 简单 避免多线程的同步问题
劣势
: 没有达到懒加载的效果 内存的浪费
2.2 饿汉式(静态代码块)
代码案例
//方式二:静态代码块的方式
class Singleton {
//构造器私有化
private Singleton() {
}
//类的内部创建对象
private static final Singleton singleton;
static {
singleton = new Singleton();
}
//对外提供公共的静态的方法
public static Singleton getInstance() {
return singleton;
}
}
案例分析
优势
: 简单 避免多线程的同步问题
劣势
: 没有达到懒加载的效果 内存的浪费
2.3 懒汉式(线程不安全)
代码案例
class Singleton{
//构造器私有化
private Singleton(){}
//类的内部提供对象
private static Singleton singleton;
//对外提供公共的静态方法的时候,来判断
public static Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
案例分析
优势
:起到了懒加载的效果 不会造成内存浪费
在使用到的时候才会创建对象。判断有无对象,有则返回,无创建后再返回
劣势
:只能在单线程下使用,多线程情况下线程不安全 不推荐这种方式的
① 在多线程的情况下,有一个对象进入if判断通过,还没执行到创建对象这一步骤时
② 有另外一个对象也进入了if判断,也通过了。
此时就会出现多个实例,造成线程不安全。
2.4 懒汉式(线程安全,同步方法)
在获取对象的静态方法上添加 synchronized 关键字,实现同步方法。解决线程不安全问题。
代码案例
//加入同步处理 同步方法
class Singleton{
//构造器私有化
private Singleton(){}
//类的内部提供对象
private static Singleton singleton;
//对外提供公共的静态方法的时候,来判断
public static synchronized Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
案例分析
解决了线程安全问题,但是效率太低
每一个线程需求拿实例的时候都要在外面等候另一线程处理完
2.5 懒汉式(线程不安全,同步代码块)
将实例化对象过程放入同步代码块中
代码案例
//加入同步处理 - 同步代码块的方式 不推荐的
class Singleton{
//构造器私有化
private Singleton(){}
//类的内部提供对象
private static Singleton singleton;
//对外提供公共的静态方法的时候,来判断
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
}
案例分析
不推荐的,解决不了线程的安全问题
这种方式本意是想解决同步方法的问题。但是我们分析一下。在多线程情况下还是有可能创建多个实例的问题。
2.6 双重检查 (推荐使用)
代码案例
class Singleton{
private Singleton(){}
//禁止指令重排
private static volatile Singleton singleton;
//加入双重检查机制
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
案例分析
优点
线程安全
解决了线程安全问题
① 多线程时,若两个线程同时满足第一层非空判断,等待调用同步代码块。
② 由于添加了synchronized ,当第一个线程进来时,发现Singleton对象为空,进行创建对象并返回。
③ 当另外一个线程调用同步代码块时,进行再次对象非空判断,发现对象已经创建成功。不在执行创建对象流程,返回已有对象
懒加载
使用到对象时才创建,不会造成内存的浪费
效率很高
先进性了singleton 是否为空的判断,singleton 如果不为空直接返回结果。倘若无第一层判空,多线程时每次都要进行synchronized 等待其他线程处理结束,才能进入内部非空判断,效率相对低。
可能出现的问题
我们认为的 new Singleton() 操作
1)分配内存地址 M
2)在内存 M 上初始化Singleton 对象
3)将M的地址赋值给 instance 对象
JVM编译优化后(指令重排)可能的 new Singleton() 操作
1)分配内存地址 M
2)将M的地址赋值给instance变量
3)在内存M上初始化 Singleton 对象
这就有可能出现空指针异常
异常发生过程
解决方式:关键字 Volatile 来禁止指令重排
扩展 - Volatile
轻量级的同步机制 (低配版) 没有保证原子性
三大特性
保证可见性
其中一个线程修改了主内存共享变量的值,要写回主内存,并要及时通知其他线程可见
没有保证原子性
没法(不能保证)不可分割,完整,要么同时成功,要么同时失败
禁止指令重排
和底层内存屏障相关 避免多线程下出现指令乱序的情况
扩展-线程切换
Java的一条语句对应的cpu指令可能是多条,其中任意一条cpu指令在执行完都可能发生线程切换
count += 1,对应cpu 指令如下:
1)将变量count从内存加载到cpu寄存器
2)寄存器中 +1
3)将结果写入内存(缓存机制写入的可能是cpu而不是内存)
2.7 静态内部类 (推荐使用)
代码案例
class Singleton{
private Singleton(){}
private static class SingletonInstance{
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
案例分析
①当Singleton类加载时,静态内部类是不会加载的。
②只有调用getInstance方法用到了SingletonInstance.INSTANCE静态变量时导致SingletonInstance静态内部类进行加载。
不会出现线程安全问题
JVM来帮我们保证了线程的安全性
利用静态内部类的特点,效率也很高,实际开发中推荐使用的
2.8 枚举方式
代码案例
public class EnumDemo {
public static void main(String[] args) {
//验证其正确性
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance == instance1); //true
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
enum Singleton{
INSTANCE; //属性
}
案例分析
不仅可以避免线程安全问题 还可以防止反序列化重新创建对象。推荐使用
三 注意事项
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
四 单例模式的使用场景
- 对于一些需要频繁创建销毁的对象
- 重量级的对象(创建对象时耗时过多,或者耗费资源过多)
- 经常使用到的对象
- 工具类对象
- 数据源,session。。。。