Java设计模式-单例模式

本文介绍了Java中四种常见的单例模式实现方式:饿汉模式、懒汉模式、静态内部类以及通过枚举实现单例。详细阐述了每种模式的特点、优缺点,并特别指出枚举是线程安全且无法通过反射破坏的单例实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4、单例模式

单例模式就是确保某个类只有一个实例,有三种实现方式:饿汉模式、懒汉模式、静态内部类。(枚举类也是实现单例)

单例有三个特点:1、私有构造函数;2、有指向自己实例的私有静态引用;以自己实例为返回值的静态公有方法。

1、饿汉模式

就是在类加载初始化的时候就主动创建实例。

优点:线程安全,调用效率高。
缺点:在还没被使用前一直占用内存,浪费内存空间。

//饿汉模式
public class Single {
    private Single(){   //注意是私有

    }

    private static Single single = new Single();   //私有且静态

    public static Single newInstance(){     //公有静态
        return single;
    }
}
class Text{
    public static void main(String[] args) {
        Single single1 = Single.newInstance();
        Single single2 = Single.newInstance();
        System.out.println(single1);
        System.out.println(single2);
    }
}

2、懒汉模式

在类初始化时不创建实例,而是要在真正使用时再创建

2.1、懒汉模式的DCL单例,线程不安全

//懒汉模式,DCL单例,线程不安全(因为变量没加volatile)
public class LazySingle {
    private LazySingle(){

    }

    private static LazySingle lazySingle = null;

    public static LazySingle newInstance(){
        //双重检测锁
        if(lazySingle==null){
            synchronized (LazySingle.class){
                if(lazySingle==null){
                    lazySingle = new LazySingle();
                    /**
                     * new有三个步骤:1、分配内存空间给对象;2、执行构造函数初始化对象;3、为对象赋给引用
                     * 但是由于变量没加volatile,所以会有重排序变成:1->3->2
                     * 那么当线程A执行到完第3步骤(lazySingle引用不为null了),执行第2步骤时线程切换,
                     * 线程B开始执行newInstance方法,判断lazySingle的引用不为null,直接return lazySingle,
                     * 那么返回将是一个未执行初始化的对象,所以现在也是线程不安全。
                     */
                }
            }
        }
        return lazySingle;
    }
}

2.2、DCL单例的完善:

为静态引用添加volatile,则线程安全。

 private static volatile LazySingle lazySingle = null;

3、静态内部类

通过静态内部类来实现单例

public class InnerSingle {
    private InnerSingle(){
    }

    public static InnerSingle newInstance(){
        return InnerClass.innerSingle;
    }
    private static class InnerClass{
        private static InnerSingle innerSingle = new InnerSingle();
    }
}

**反射破除以上单例:**其实上面的单例可以用反射机制来进行破除(就是能创建多个实例,而不是单个),以懒汉模式为例子:

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, 						InvocationTargetException, InstantiationException {
        Constructor<LazySingle> constructor = LazySingle.class.getDeclaredConstructor();
        constructor.setAccessible(true);   //这个方法设置为true,运行访问私有变量及私有函数。
        LazySingle lazySingle1 = constructor.newInstance();
        LazySingle lazySingle2 = constructor.newInstance();
        System.out.println(lazySingle1);    //single.LazySingle@4554617c
        System.out.println(lazySingle2);	//single.LazySingle@74a14482
    }
}

通过反射获得了多个单例,那么如何真正获得单例对象呢?点进constructor.newInstance看一下:

在这里插入图片描述

通过上述代码,我们知道了枚举类才是真正的单例!!!

4、通过枚举实现单例

public enum EnumSingle {
    INSTANCE("我是单例的",5);

    String name;
    int i;
    EnumSingle(String name,int i){
        this.name = name;
        this.i = i;
    }

}

class Test3{
    public static void main(String[] args) {
        EnumSingle single1 = EnumSingle.INSTANCE;
        EnumSingle single2 = EnumSingle.INSTANCE;
        System.out.println(single1==single2);    //true
        System.out.println(single1.name);   //输出:我是单例的
        System.out.println(single1.i);         //输出:5
    }
}

通过反射创建枚举类将会报错,代码如下:

class Test3{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        constructor.newInstance("你好",5);  //报错
    }
}

报的错是NoSuchMethodException,说明构造函数不是这个,在底层枚举类的构造函数被更改,需要使用专业软件反编译才能知道。
java设计模式大体上分为三大类: 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 设计模式遵循的原则有6个: 1、开闭原则(Open Close Principle)   对扩展开放,对修改关闭。 2、里氏代换原则(Liskov Substitution Principle)   只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 3、依赖倒转原则(Dependence Inversion Principle)   这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。 4、接口隔离原则(Interface Segregation Principle)   使用多个隔离的借口来降低耦合度。 5、迪米特法则(最少知道原则)(Demeter Principle)   一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 6、合成复用原则(Composite Reuse Principle)   原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值