唯我独尊-单例模式

概述

单例模式(Singleton pattern),从字名意思来看就知道只会产生一个实例对象,属于创建类型的一种常用设计模式

Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

一般这种专业的定义都写的晦涩难懂,我们来理解一下,一个类有且仅有一个实例,说的是这个类只会产生一个对象,任何其他对象对它的依赖都是一样的,访问的是同一个对象。自行实例化向整个系统提供,说的是单例模式不需要你自己去new一个对象,你去访问的这时候对象已经自行实例化好了,直接拿来主义即可。简单理解就是:

  • 只会生产一个对象
  • 拿来主义,不需要主动去new一个对象,直接拿来用就好

优缺点

优点

  • 单例模式只在内存中产生了一个实例对象,减少了内存开支
  • 减少系统性能的开销,比如创建一个对象需要消耗很多系统资源,如读取配置文件等,则可以直接使用单例来创建一个对象,常驻内存
  • 全局访问对象。当系统中有一些共享资源需要全局唯一,就可以使用单例模式来实现

缺点

  • 单例模式抽象,不容易扩展
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  • 滥用单例将带来一些负面问题,比如,为了节省资源将数据库连接对象设置为单例,可能会导致共享连接池对象的程序过多而出现连接池溢出

使用场景

单例模式要求只能产生一个实例对象,那么产生多个对象会有问题的场景,都是单例的使用场景:

  • 要求生成唯一序号的场景
  • 系统中共享的数据
  • 创建对象消耗的开销过大,也适合用单例模式

实现方式

/**
 * 单例模式-饿汉式
 */
public class SingletonDemo1 {
    private static SingletonDemo1 instance = new SingletonDemo1();
    //构造函数私有化,禁止new对象
    private SingletonDemo1(){

    }

    public static SingletonDemo1 getInstance(){
        return instance;
    }
}

饿汉式是最简单的单例模式实现,私有化构造函数,getInstance()方法直接返回一个内部静态实例对象,缺点很明显:单例对象一开始就创建好了,无论是否使用,对象都会进行初始化,假如对象一直没有被使用将造成一种资源浪费。

/**
 * 单例模式-懒汉式
 */
public class SingletonDemo2 {

    private static SingletonDemo2 instance = null;

   //构造函数私有化,禁止new对象
    private SingletonDemo2(){

    }

    public static synchronized SingletonDemo2 getInstance(){
        if(instance == null){
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

懒汉式解决了延迟初化化的问题,对象在使用时进行创建对象,但是在getInstance()方法上加了锁,同时只有一个线程可进行getInstance()的调用,线程安全但性能不高

/**
 * 单例模式-Double CheckLock实现
 */
public class SingletonDemo5 {
    private volatile static SingletonDemo5 instance;
    private SingletonDemo5(){

    }
    public static SingletonDemo5 getInstance(){
        if(instance == null){
            synchronized (SingletonDemo5.class){
                if(instance == null){
                    instance = new SingletonDemo5();
                }
            }
        }
        return instance;
    }
}

Double CheckLock实现,由于Java编译器允许处理器指令重排序,故Double CheckLock实现存在安全隐患,不推荐使用

/**
 * 单例模式-静态内部类方式
 */
public class SingletonDemo3 {
    private static class SingletonClass{
        private static final SingletonDemo3 instance = new SingletonDemo3();
    }

    //构造函数私有化,禁止new对象
    private SingletonDemo3(){

    }

    public static SingletonDemo3 getInstance(){
        return SingletonClass.instance;
    }
}

静态内部类方式,可以实现延迟加载,线程安全,调用的效率高。

以上是单例模式最常见的三种实现方式,看似静态内部类是最好的实现方式,其实上面几种模式看似实现了单例模式,但都存在共同的缺陷。尽管构造函数被设置成私有的,但仍然可以通过反射来创建对象,这样就违背了单例的原则。

还有一种枚举的实现方式,可以解决以上几种实现方式的问题:

/**
 * 单例模式-枚举方式
 */
public enum SingletonDemo4 {

    INSTANCE;

    public void method(){
        System.out.println("doSomething");
    }
}

public class SingletonTest {
    public static void main(String[] args){
        //直接访问method()方法
        SingletonDemo4.INSTANCE.method();
    }
}

枚举方式线程安全、调用效率高,可以天然防止反射、反序列化的调用,但它不能延迟加载

如何选择

我们看到单例模式看似简单,但实现起来并没有想像中的那么简单,需要考虑各种特殊的情况。那么,我们在实际使用过程中该如何选择使用哪种方式来实现呢?

  • 当单例对象占用资源少,不考虑延迟加载时,枚举优于饿汉
  • 当单例对象占用资源多,需要延时加载时,静态内部类优于懒汉

总结一下

  • 单例模式只会产生一个对象,所有依赖访问的都是相同的对象
  • 单例模式优点
    • 减少了内存开支
    • 减少系统性能的开销
    • 资源全局统一访问
  • 单例模式缺点
    • 单例模式抽象,不容易扩展
    • 单例类的职责过重,在一定程度上违背了“单一职责原则”。
    • 滥用单例将带来一些负面问题
  • 单例模式的使用场景
    • 要求生成唯一序号的场景
    • 系统中共享的数据
    • 创建对象消耗的开销过大,也适合用单例模式
  • 实现方式
    • 饿汉式(线程安全,调用效率高,不能延时加载,可反射破坏单例)
    • 懒汉式(线程安全,调用效率不高,但是能延时加载,可反射破坏单例)
    • Double CheckLock实现方式
    • 静态内部类方式(线程安全,调用效率高,可以延时加载,可反射破坏单例)
    • 枚举方式(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)
  • 如何选择
    • 当单例对象占用资源少,不考虑延迟加载时,枚举优于饿汉
    • 当单例对象占用资源多,需要延时加载时,静态内部类优于懒汉
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值