创建型设计模式 之 单例模式

《研磨设计模式》中的定义:保证一个类仅有一个实例,并提供它的一个全局访问点。

而单例模式的本质便是——控制实例数目。

一、介绍

1、单例模式和静态方法区别

简单的一个懒汉式单例模式

public class EagerSingleton {
    private static EagerSingleton instance = new EagerSingleton();
    /**
     * 私有默认构造子
     */
    private EagerSingleton(){}
    /**
     * 静态工厂方法
     */
    public static EagerSingleton getInstance(){
        return instance;
    }
}

静态方法

class Test{
    public static int z(int xx,int yy){
        return xx+yy;
    }
    public int zz(int xx,int yy){
        return xx+yy;
    }
}

调用对比

public static void main(String args[]){
	// 调用静态方法
	sum=Test.z(1,2); 

	// 调用非静态方法
	Test t=new Test();
	sum=t.zz(1,2); 
	
	// 调用单例模式
	EagerSingleton instance = EagerSingleton.getInstance();
}

1、单例模式

  关于单例模式的概念这里不再多说,单例模式的好处有如下几个:

  1)只有一个实例对象,节省内存空间

  2)支持延迟加载

  3)由于单例对象一般用static修饰,所以一次创建长久使用,即不会被GC回收

2、静态方法

  相对于单例模式,静态方法的优势如下:

  1)没有实例对象,直接使用方法

  2)速度更快

  但静态方法也有劣势

  1)不能延迟加载,JVM启动时自动加载static修饰的对象

  2)耦合性强,这里所说的耦合性强是指静态方法无法被重写,因为重写是针对非静态方法而言,当方法需要变更的时候必须进行修改操作,违反了开闭原则。可能有的童鞋没有理解,举例言之,接口A是我们的dao层方法规范,我们通过实现类A1来完善方法功能,如果有变更,可以新建一个实现类或A1的子类A2来完成,此时调用方不需要做任何的更改;而如果使用静态方法,则需要修改调用方

  3)事务控制更加麻烦,我们在用spring做事务控制的时候是基于AOP实现的,其原理是使用JDK的动态代理或者cglib来实现,不管使用哪一种,都需要一个实例对象来创建代理对象,而使用静态方法时由于没有实例对象,所以也就无法使用这种声明式事务了

3、总结

  个人推荐优先使用单例模式,那么什么时候使用静态方法呢?对于一个类,满足单例情况且不会出现上面所说的耦合性和事务问题即可使用静态方法,如java中的Math类。我刚接手的项目中是对redis的一个简单查询功能,满足上述的情况,所以也适用静态方法来实现。

单例模式介绍

1、单例模式是比较简单的模式,传统的有懒汉式和饿汉式两种。

懒汉式如下:

public class Singleton1 {

    private static Singleton1 instance = null;

    private Singleton1() {
    }

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

顾名思义,懒汉式便是类加载时并不创建该单例对象实例,等到类使用时才去创建。

2、饿汉式如下:

public class Singleton2 {

    private static Singleton2 instance = new Singleton2();

    private Singleton2() {
    }

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

  

饿汉式便是类加载时便创建实例了,故每次使用时无需创建。

懒汉式、饿汉式就是Spring中的BeanFactory和ApplicationContext在处理单例类时的区别。

3、由于懒汉式单例模式并非线程安全的,想要保证线程安全,除了在创建实例的方法上加Synchronized之外,最简单的当然是,在创建对象的时候,每一步多进行一次判断,判断该对象是否已经存在。

二、优雅实现

然而,《研磨设计模式》中介绍了Java中一种一优雅的,能保证线程安全的懒汉式单例模式,叫做Lazy initialization holder class模式。如下:

public class Singleton3 {

    private Singleton3() {
    }

    private static class SingletonHolder {
        private static Singleton3 instance = new Singleton3();
    }

    public static Singleton3 getInstance() {
        return SingletonHolder.instance;
    }
}

  

类的静态内部类,实际上是一个顶层类,可以不依赖外围类的实例而存在,就像是类的一个静态成员变量。只有在使用时,其内部的变量才会被实例化。Lazy initialization holder class模式便是利用JVM自身的特性保证了线程安全。

三、推荐使用

枚举类型

public enum User {
    /**
     * 单例
     */
    Instance;

    private User() {
        System.out.println("初始化User");
    }

    public void print() {
        System.out.println("tttttt");
    }
}

调用

User user = User.Instance;
String code = user.getCode();

使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
你可以通过EasySingleton.INSTANCE来访问,这比调用getInstance()方法简单多了。

优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:
1、没有接口,不能继承,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
2、通过控制自己的创建和生命周期,违反了单一责任原则SRP(Single Responsibility Principle)
3、创建紧密耦合的代码,单例模式的客户端变得难以测试
总结:
使用单例模式考虑线程安全和性能,推荐使用静态内部类 和 枚举

原文地址:https://www.cnblogs.com/JiangWJ/p/10807764.html