设计模式 - 单例模式

186 阅读8分钟

我使用单例模式的初衷就是保持同一个对象,内部为同一份数据,同时全局均可调用 ~

设计模式分为三种类型,共23种

  • 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  • 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)、访问者模式

基础了解

单例模式属于创造型模式的一种,在开发中也是最为常用的一种设计模式,其存在实现了数据同步化,同时减少了内存的开支,提升了一定的开发效率~

单例特性

  • 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new 实例 ,主要实现了统一出口的原则,在开发中一定程度上减少了耦合性

适用场景

  • 需要 频繁的进行创建和销毁的对象
  • 创建对象时耗时过多或耗费资源过多
  • 经常用到的对象
  • 工具类对象
  • 频繁访问数据库或文件的对象(比如 数据源、session 工厂等)

实战演练

我个人使用懒汉式、双检锁多一点 ~

懒汉式

核心在于字,懒的原由在于除首次新建实例之外,以后若再次调用此实例的话,均是同一实例,减少了内存开销 ~

常规而言懒汉式的具体方式其实有三种,主要与synchronized是否声明、声明位置有关

  • 线程不安全
  • 线程安全,同步方法
  • 线程安全,同步代码块

在多线程中要注意同步代码块,否则容易在if的判断期间执行多次实例创建 ~ 有的人会说这样做效率比较低,其实我想说真低不到哪里去 ~ 我认为除非是代码优化到一定高度才在这里进行二次优化

public class Singleton {
    private Singleton() {
    }

    private static Singleton singleton;

    //静态获取实例,没有就创建,有就返回
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

饿汉式

核心在于饿字,当使用该实例时每次都会返回一个新建的实例,内存开销相对要对懒汉模式更大一些 ~

优点:写法简单,就是在类装载的时候就完成实例化,避免了线程同步问题
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果;如果从始至终从未使用过这个实例,则会造成内存的浪费。

public class Singleton {
    public Singleton() {
    }

    public static Singleton singleton = new Singleton();

    //静态工厂方法
    public static Singleton getInstance() {
        return singleton;
    }
}

双检锁

双检锁也称双锁检验,更多的是基于懒汉式进行二次校验,这里主要采用了synchronized关键字,在一次判断之后进行线程安全的声明,然后在进行二次判断;这样写的方式减少了内存开销,提供了安全线程,执行效率也不错 ~

public class Singleton {
    private Singleton() {
    }

    private static Singleton singleton;

    public static Singleton getInstance() {
    	//非空校验,第一把锁
        if (singleton == null) {
        	//线程安全,二次判断(相当于第二把锁)
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

静态内部类

静态内部类的方式效果类似双检锁,但实现更简单;不过这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用 ~

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的 ~

public class Singleton {
    private Singleton() {
    }

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

枚举

简洁清晰,自动支持序列化机制,绝对防止多次实例化

public enum Singleton {
    INSTANCE;

    public void method() {

    }
}

思维扩展

以下内容为自我答疑过程,以后或许会移植到一篇新Bolg内

一般情况下,懒汉式(包含线程安全和线程不安全梁总方式)都比较少用;饿汉式和双检锁都可以使用,可根据具体情况自主选择;在要明确实现 lazy loading 效果时,可以考虑静态内部类的实现方式;若涉及到反序列化创建对象时,大家也可以尝试使用枚举方式。

内部类场景

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
  • 内部类可以对同一个包中的其他类隐藏起来
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷

静态域、公有域和实例域的区别

什么是域?在我认为成员变量和局部变量就是域的一种体现,他们因域的不同所以执行效果也不尽相同 ~

静态域=公有域
将域定义为static,一个类中只有一个这样的域,

对象首次初始化时将copy一份到堆内,方法区外,作为静态对象 ;而带static修饰的类,方法,字段,代码块都会被copy一份到静态域中,同时独立且唯一地存在于这个静态对象中 ~ 有的人喜欢称其为共享变量 ~

针对于被static修饰的变量、代码块、方法、内部类

变量: 静态变量会copy一份到堆内, 方法区外的静态对象中,那么它属于静态域,可被所有线程共享,一旦成为共享变量后,最好使用原子操作类替代

代码块:静态代码块会copy一份到堆内,方法区外的静态对象中,那么它属于静态域,只会在第一次初始化对象时执行一次,不可访问非静态的部分

方法、内部类: 静态方法或静态内部类会copy一份到堆内, 方法区外的静态对象中,那么它属于静态域,不可访问非静态的部分,但可被所有线程共享

实例域
每一个对象对于所有的实例域都有自己的一份拷贝,所有的非final,非static的对象都存储在实例域中,只可被当前实例查看及更新

域的初始化与赋值

两种情况

  • 在建立对象即进行类的实例化时域的初始化
  • 在不建立对象,只装载类的时候域的初始化

有两种情况是只装载类而不实例化类

  • 用java classname执行程序时
  • 用classname.statement调用类的静态域或静态方法时

赋值方式

  • 赋予默认赋值
  • 声明变量时同时赋值
  • 块赋值(实例块和静态块)
  • 构造器赋值

静态内部类和非静态内部类之间得不同

  • 内部静态类不需要有指向外部类的引用;但非静态内部类需要持有对外部类的引用
  • 非静态内部类能够访问外部类的静态和非静态成员;静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员
  • 一个静态内部类不能脱离外部类实体被创建;一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面

懒汉式和饿汉式的区别

相对代码而言有俩点

  • 懒汉式多一个if的判空
  • 饿汉式不需要if的判空

相比思想而言

  • 特点也在其之上的基础,懒汉式首先的初始化,同时之后每次都会通过判空才进行处理,如果其已经存在的话,直接调用已有的那个值
  • 饿汉式而言呢,在于每一次去查找实例的时候,直接就是返回一个新的对象。

结果
俩者相比而言,属于本质区别的!懒汉式是创建一次实例一直使用,饿汉式是每次都要开启新的对象,浪费内存~

饿汉式与静态内部类的区别

俩种方式机制类似,但又有不同 ~

  • 相同点
    都采用了类装载的机制来保证初始化实例时只有一个线程
  • 不同点
    饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用 ~
    静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化 ~

方式分类

此处是看别人文章而来,记录一番

  • 枚举 好于 饿汉

占用资源少,不需要延时加载

  • 静态内部类 好于 懒汉式

占用资源多,需要延时加载