设计模式——单例模式
目录
? ?2、结构
? ?3、实现
一、为什么要使用单例模式? 🪴
在不使用单例模式之前,我们每次 new 的对象都不是同一个对象!
? ? ? ? 导致代码与特定类耦合度较高。这使得在后续需要更改对象类型时,需要修改并重新编译涉及到该对象的所有代码。这样一来,代码的灵活性就受到限制,无法方便地切换或替换为其他相关的类。我们通过代码来理解。
? 然后我们编写测试类?
? ? ? ?现在我们有个需求:我第一个对象不止一个name属性,我还有个年龄属性,该怎么办呢?这很好办,就在Student类加上age属性。
- ? ?现在我们只加了一个属性,只有两个对象,万一我加十个属性,有十个、二十个对象呢。这样是不是就会很麻烦!
二、什么是单例模式🌸
? ?1、概述
定义:确保一个类只有一个实例,并提供一个全局访问该实例的入口。
? ? ? 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
2、结构
单例模式的主要有以下角色:
-
单例类。只能创建一个实例的类
-
访问类。使用单例类
3、实现
单例设计模式分类两种:
????????饿汉式:类加载就会导致该单实例对象被创建??
????????饿汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
Ⅰ、饿汉式 (预加载)
预加载:顾名思义,就是预先加载。再进一步解释就是还没有使用该单例对象,但是,该单例对象就已经被加载到内存了。
①??饿汉式(静态变量方法)
package com.cskt.singleton;
/**
* @Description TODO
* @Author Yii_oo
* @CreateDate 2023-09-07 9:15
* @Version 1.0
* @ClassDesc 饿汉式:静态成员变量
*/
public class SingletonDemo1 {
//定义静态成员变量获取本类的实例
private static final SingletonDemo1 INSTANCE = new SingletonDemo1();
//私有构造方法,避免通过new关键字来实例化对象,保证只存在一个实例
private SingletonDemo1(){}
//提供一个公共的访问类,让外界获取该对象
public static SingletonDemo1 getINSTANCE() {
return INSTANCE;
}
}
②??饿汉式(静态代码块方法)
package com.cskt.singleton;
/**
* @Description TODO
* @Author Yii_oo
* @CreateDate 2023-09-07 12:01
* @Version 1.0
* @ClassDesc 饿汉式:静态代码块
*/
public class SingletonDemo2 {
//声明Singleton2类型的静态常量成员变量,但是没有初始化,在静态代码块中对其进行初始化
private static SingletonDemo2 INSTANCE;
//在静态代码块中对成员常量初始化
static {
INSTANCE = new SingletonDemo2();
}
//私有化构造方法,避免外部使用new关键字创建实例
private SingletonDemo2(){}
//定义公共方法,对外提供获取单例实例的接口
public static SingletonDemo2 getINSTANCE() {
return INSTANCE;
}
}
注:
????????很明显,没有使用该单例对象,该对象就被加载到了内存,会造成内存的浪费。
????????该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
③? ?饿汉式? (枚举方式)? :线程安全模式
? ? ? ? 枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
package com.cskt.singleton;
/**
* @Description TODO
* @Author Yii_oo
* @CreateDate 2023-09-08 10:09
* @Version 1.0
* @ClassDesc : 枚举方式实现单例
* 枚举类型实现单例模式是极力推荐的实现模式。因为枚举是线程安全的,并且只会装载一次,设计中充分。
* 利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所单例实现中唯一不会被破坏的单例实现模式
* 枚举类型属于饿汉式方式,即预加载模式。在不考虑内存空间的情况下,使用枚举方式是比较好的一种方式
*/
public enum SingletonEnum {
INSTANCE;
}
Ⅱ、懒汉式(懒加载)
为了避免内存的浪费,我们可以采用懒加载,即用到该单例对象的时候再创建。
① 方式一 对外提供获取单例接口的方法?
package com.cskt.singleton;
/**
* @Description TODO
* @Author Yii_oo
* @CreateDate 2023-09-08 9:29
* @Version 1.0
* @ClassDesc 懒汉式 方式一
*/
public class SingletonDemo4 {
//声明 Singleton 类型的变量
private static SingletonDemo4 instance; //只是申明,并没有赋值
//私有构造方法
private SingletonDemo4() {}
/**
* 第一种方式:
* 对外提供获取单例的接口方法,下面这种方法存在线程安全问题。
*/
public static SingletonDemo4 getInstance() {
if(instance==null){
instance = new SingletonDemo4();
}
return instance;
}
}
注:
? ? ? 从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
?② 解决线程安全问题
package com.cskt.singleton;
/**
* @Description TODO
* @Author Yii_oo
* @CreateDate 2023-09-08 10:22
* @Version 1.0
* @ClassDesc 懒汉式 (线程安全方式)
*/
public class SingletonDemo5 {
//声明 Singleton 类型的变量
private static SingletonDemo5 instance; //只是申明,并没有赋值
//私有构造方法
private SingletonDemo5() {}
/**
* 第二种方式:
* 使用 synchronized 同步关键字来解决线程安全问题
* synchronized 加载 getInstance 函数上确保了线程的安全。
* 但是,如果要经常的调用 getInstance ()方法,不管有没有初始化实例,都会幻
*/
public static SingletonDemo5 getInstance() {
if(instance==null){
instance = new SingletonDemo5();
}
return instance;
}
}
注:
? ? ?上面代码的方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了
③? 双重检查
? ? 再来讨论一下懒汉模式中加锁的问题,对于?
getInstance()
?方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
package com.cskt.singleton;
import java.io.Serializable;
/**
* @Description TODO
* @Author Yii_oo
* @CreateDate 2023-09-08 10:22
* @Version 1.0
* @ClassDesc 懒汉式的双重检查锁方式
*/
public class SingletonDemo6 implements Serializable {
//使用volatile 关键字可以保证可见性和有序性
private static volatile SingletonDemo6 instance;
private SingletonDemo6(){}
public static SingletonDemo6 getInstance() {
/**
* 使用双重检查解决了单例、性能、线程安全问题,但是在多线程环境中由于JVM在实例化对象的时候会进行
* 优化和指令的重排序操作,可能会导致空指针异常问题,这时候需要使用volatile关键字,可以保证可见性
* 和有序性
*/
//第一次判断,如果instance的值不为null,不需要抢占锁,再返回对象
if(instance==null){
synchronized (SingletonDemo6.class){
//第二次判断
if(instance==null){
instance = new SingletonDemo6();
}
}
}
return instance;
}
}
注:?
? ? ?双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。
添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。
④? 静态内部类
? ? ? 静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被?
static
?修饰,保证只被实例化一次,并且严格保证实例化顺序。
package com.cskt.singleton;
/**
* @Description TODO
* @Author Yii_oo
* @CreateDate 2023-09-08 10:22
* @Version 1.0
* @ClassDesc 懒汉式方式4,静态内部类方式
* 静态内部类单例模式中实例由内部类创建,由于JVM在加载外部内的过程中,是不会加载静态内部类的,
* 只有内部类 的属性或者方法被调用时,才会被加载,并初始化其静态属性。静态属性由于被static修饰,所以只会被实例化一次,
* 并且严格保证实例化顺序
*/
public class SingletonDemo7 {
private static boolean flag = false;
//私有化构造器
private SingletonDemo7(){
}
//定义静态内部类,私有化属性
private static class SingletonHolder{
private static final SingletonDemo7 INSTANCE = new SingletonDemo7();
}
//对外提供静态方法获取该对象
public static SingletonDemo7 getInstance() {
return SingletonHolder.INSTANCE;
}
}
注:?
? ? ? ?第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
? ? ? ? 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!