单例模式(Singleton)

什么是单例模式?

保证整个系统中一个类只有一个对象的实例,实现这种功能的方式就叫单例模式

为什么要用单例模式?

1、单例模式节省公共资源

比如:大家都要喝水,但是没必要每人家里都打一口井是吧,通常的做法是整个村里打一个井就够了,大家都从这个井里面打水喝。

对应到我们计算机里面,像日志管理、打印机、数据库连接池、应用配置。

2、单例模式方便控制

就像日志管理,如果多个人同时来写日志,你一笔我一笔那整个日志文件都乱七八糟,如果想要控制日志的正确性,那么必须要对关键的代码进行上锁,只能一个一个按照顺序来写,而单例模式只有一个人来向日志里写入信息方便控制,避免了这种多人干扰的问题出现。

实现单例模式的思路

1. 构造私有:

如果要保证一个类不能多次被实例化,那么我肯定要阻止对象被new 出来,所以需要把类的所有构造方法私有化。


2.以静态方法返回实例

因为外界就不能通过new来获得对象,所以我们要通过提供类的方法来让外界获取对象实例。

3.确保对象实例只有一个

只对类进行一次实例化,以后都直接获取第一次实例化的对象。

/**
 * 单例模式案例
 */
public class Singleton {
	//确保对象实例只有一个。
 private static final Singleton singleton = new Singleton();
	//构造方法私有
 private Singleton() {
    }
 //以静态方法返回实例
 public static Singleton getInstance() {
 return singleton;
    }
}

这里类的实例在类初始化的时候已经生成,不再进行第二次实例化了,而外界只能通过SingleCase.getInstance()方法来获取SingleCase对象, 所以这样就保证整个系统只能获取一个类的对象实例。

单例模式的五种实现方式(饿汉式、懒汉式、DCL懒汉式、静态内部类式、枚举单例)、优缺点比较

一、饿汉式

饿汉模式,可以想象一个很饿的人,需要立马吃东西,饿汉模式便是这样,在类加载时就创建对象,由于在类加载时就创建单例,因此不存在线程安全问题

//饿汉式
public class SingletonDemo1 {
    //私有化构造器
    private SingletonDemo1() {
    }
    //类初始化时立即加载该对象
    private static SingletonDemo1 instance = new SingletonDemo1();
    //提供公共的获取方法,由于静态的instance在类加载时就创建,因此不存在线程安全问题
    public static SingletonDemo1 getInstance() {
        return instance;
    }
}
//测试
class SingletonDemo1Test {
    public static void main(String[] args) {
        SingletonDemo1 instance = SingletonDemo1.getInstance();
        SingletonDemo1 instance1 = SingletonDemo1.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

但饿汉式也存在一定的问题,即如果在该类里面存在大量开辟空间的语句,如很多数组或集合,但又不马上使用他们,这时这样的单例模式会消耗大量的内存,影响性能

二、懒汉式

顾名思义,懒汉式,就是懒,即在类加载时并不会立马创建单例对象,而是只生成一个单例的引用,即可以延时加载

//懒汉模式
public class SingletonDemo2 {
    //私有化构造器
    private SingletonDemo2() {
    }
    //只提供一个实例,并不创建对象
    private static SingletonDemo2 instance;
    //提供公共的获取方法,因为不是在类加载时就创建对象,因此存在线程安全问题,使用synchronized关键字保证线程安全,效率降低
    public static synchronized SingletonDemo2 getInstance() {
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}
//测试
class SingletonDemo2Test {
    public static void main(String[] args) {
        SingletonDemo2 instance = SingletonDemo2.getInstance();
        SingletonDemo2 instance1 = SingletonDemo2.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

懒汉式使用同步锁锁住了整个方法,效率较低

三、DCL懒汉式(双重检测锁模式)

同样是在类加载时只提供一个引用,不会直接创建单例对象,不需要对整个方法进行同步,缩小了锁的范围,只有第一次会进入创建对象的方法,提高了效率

//DCL懒汉式(双重检测锁模式)
public class SingletonDemo3 {
    //私有化构造器
    private SingletonDemo3() {
    }
    //只提供一个实例,并不创建对象
    //使用避免指令重排带来的线程安全问题
    //volatile:对于同一个变量,在一个线程中值发生了改变,则在另一个线程中立即生效,可以大幅度避免下面的问题,不排除极端情况
    private static volatile SingletonDemo3 instance;
    //提供公共的获取方法,因为不是在类加载时就创建对象,因此存在线程安全问题,使用同步代码块提高效率
    //现在不需要对整个方法进行同步,缩小了锁的范围,只有第一次会进入创建对象的方法,提高了效率
    //当第一个线程执行到创建对象的方法时,但还未出方法返回,此时第二个线程进入,发现instance不为空,但第一个线程此时还未出去,可能发送意想不到的安全问题
    public static SingletonDemo3 getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo3.class) {
                if (instance == null) {
                    instance = new SingletonDemo3();
                }
            }
        }
        return instance;
    }
}
//测试
class SingletonDemo3Test {
    public static void main(String[] args) {
        SingletonDemo3 instance = SingletonDemo3.getInstance();
        SingletonDemo3 instance1 = SingletonDemo3.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

由于JVM底层内部模型的原因,偶尔会出现问题,因此不建议使用

 

四、静态内部类式

使用静态内部类解决了线程安全问题,并实现了延时加载

 

//静态内部类实现
public class SingletonDemo4 {
    private SingletonDemo4() {
    }
    //不会在外部类初始化时就直接加载,只有当调用了getInstance方法时才会静态加载,线程安全,final保证了在内存中只有一份
    private static class InnerClass{
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }
    public static SingletonDemo4 getInstance() {
        return InnerClass.instance;
    }
}
//测试
class SingletonDemo4Test {
    public static void main(String[] args) {
        SingletonDemo4 instance = SingletonDemo4.getInstance();
        SingletonDemo4 instance1 = SingletonDemo4.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

五、枚举单例

严格意义上来说以上四种方式实现的单例模式都不是线程安全的,因为反射机制的存在,反射可以破坏私有属性,并且通过反射创建对象,举个例子,通过反射破坏上面的静态内部类方式实现的单例模式

import java.lang.reflect.Constructor;
//静态内部类实现
public class SingletonDemo4 {
    private SingletonDemo4() {
    }
    //不会在外部类初始化时就直接加载,只有当调用了getInstance方法时才会静态加载,线程安全,final保证了在内存中只有一份
    private static class InnerClass{
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }
    public static SingletonDemo4 getInstance() {
        return InnerClass.instance;
    }
}
//测试
class SingletonDemo4Test {
    public static void main(String[] args) throws Exception{
        SingletonDemo4 instance = SingletonDemo4.getInstance();
        SingletonDemo4 instance1 = SingletonDemo4.getInstance();
        System.out.println(instance == instance1); //true
        Constructor<SingletonDemo4> declaredConstructor = SingletonDemo4.class.getDeclaredConstructor();
        //关闭权限检测
        declaredConstructor.setAccessible(true);
        SingletonDemo4 instance2 = declaredConstructor.newInstance();
        System.out.println(instance == instance2); //false
    }
}

由上面的例子可以得出,反射是可以破坏以上四种的单例模式(这里不一一演示)

那怎样才能解决这个问题呢,我们来看一下反射创建对象的newInstance()方法:

 从源码中可以看出,当反射遇到枚举时直接抛出异常,因此,枚举是创建单例的不二之选

//枚举方式实现单例模式
public enum SingletonDemo5 {
    INSTANCE;
    public static SingletonDemo5 getInstance() {
        return INSTANCE;
    }
}
class SingletonDemo5Test {
    public static void main(String[] args) {
        SingletonDemo5 instance = SingletonDemo5.getInstance();
        SingletonDemo5 instance1 = SingletonDemo5.getInstance();
        System.out.println(instance == instance1); //true
    }
}

 我们这是再使用反射尝试破坏一下到单例,会发现不能成功

六、五种实现单例模式的方式的对比

  • 饿汉式:线程安全(不排除反射),调用效率高,不能延时加载
  • 懒汉式:线程安全(不排除反射),调用效率不高,可以延时加载
  • DCL懒汉式:由于JVM底层模型原因,偶尔出现问题,不建议使用
  • 静态内部类式:线程安全(不排除反射),调用效率高,可以延时加载
  • 枚举单例:线程安全,调用效率高,不能延时加载

七、单例模式常见场景

  • Windows的任务管理器、回收站等
  • servlet中每个servlet都是单例
  • 数据库连接池一般都是单例的
  • Spring中每个Bean都是单例的

 

 单例模式的五种实现方式(饿汉式、懒汉式、DCL懒汉式、静态内部类式、枚举单例)、优缺点比较_写出几种单例模式实现,懒汉模式和饿汉模式区别_✎重逢之时的博客-CSDN博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值