Java设计模式(一) -- 单例模式

版权声明:欢迎转载,请说明出处~~~ https://blog.csdn.net/java_1996/article/details/85499888

本文主要介绍了设计模式的六大原则,并结合实例描述了各种单例模式的具体实现和性能分析测试。包括:饿汉式静态内部类懒汉式双重校验锁枚举等。

更多Java设计模式系列文章,欢迎访问我的个人博客–>幻境云图

1. 设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。

2、里氏代换原则(Liskov Substitution Principle)

其官方描述比较抽象,可自行百度。实际上可以这样理解:

(1)子类的能力必须大于等于父类,即父类可以使用的方法,子类都可以使用。

(2)返回值也是同样的道理。假设一个父类方法返回一个List,子类返回一个ArrayList,这当然可以。如果父类方法返回一个ArrayList,子类返回一个List,就说不通了。这里子类返回值的能力是比父类小的。

(3)还有抛出异常的情况。任何子类方法可以声明抛出父类方法声明异常的子类。而不能声明抛出父类没有声明的异常。

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

5、迪米特法则(最少知道原则)(Demeter Principle)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

原则是尽量使用合成/聚合的方式,而不是使用继承。

Java 中一般认为有 23 种设计模式,总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录

模式、状态模式、访问者模式、中介者模式、解释器模式。

比较常用的有:工厂方法模式、抽象工厂模式、单例模式、建造者模式、适配器模式、代理模式、享元模式、策略模式、观察者模式。

2. 单例模式

2.1 单利模式介绍

作用: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决: 一个全局使用的类频繁地创建与销毁。

优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。

缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

如果单件模式实例在系统中经常会被用到,饿汉式是一个不错的选择。
反之如果单件模式在系统中会很少用到或者几乎不会用到,那么懒汉式是一个不错的选择。

2.2 单利模式实现

1. 饿汉式

public class Singleton {  
    //类变量在类准备阶段就初始化了然后放在<clinit>构造方法中
    //一旦外部调用了静态方法,那么就会初始化完成。
    //一个类的<clinit>只会执行一次
    private static Singleton instance = new Singleton();  

    private Singleton (){}  

    public static Singleton getInstance() {  
      return instance;  
    }  

}

这种方式实现的单例:类加载时就创建实例。由classloder保证了线程安全。

2. 静态内部类

public class Singleton {  
    private static class SingletonHolder {  

    private static final Singleton INSTANCE = new Singleton();  

    }  

    private Singleton (){}  

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

}

这种方式实现的单例:实现了lazy loading 使用时才创建实例,由classloder保证了线程安全。

饿汉式/静态内部类是如何保证线程安全的:

在《深入理解JAVA虚拟机》中,有这么一句话:

虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。

3. 懒汉式

public class Singleton {  
    private static Singleton instance;  

    private Singleton (){}  

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

}

这种方式实现的单例:实现了lazy loading 使用时才创建实例。synchronized保证了线程安全,但效率低。

4. 双重校验锁

public class Singleton {
    private static volatile Singleton singleton;

    private Singleton() {
    }

    public static Singleton singleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();//非原子操作
                }
            }
        }
        return singleton;
    }
}
//singleton = new Singleton(); 非原子操作 分为三步
// 1.给singleton分配内存
// 2.调用 Singleton 的构造函数来初始化成员变量
// 3.将给singleton对象指向分配的内存空间(此时singleton才不为null)
// 指令重排序-->执行命令时虚拟机可能会对以上3个步骤交换位置 最后可能是132这种 分配内存并修改指针后未初始化 多线程获取时可能会出现问题。
//volatile关键字会禁止指令重排序 即可避免这种问题。

这种方式实现的单例:实现了lazy loading 使用时才创建实例。synchronized保证了线程安全,volatile禁止指令重排序保证了多线程获取时不为空。但要JDK1.5以上才行。

5. 枚举

public enum Singleton {
	 //定义一个枚举的元素,它就是 Singleton 的一个实例
    INSTANCE;  
    
    public void doSomeThing() {  
	     System.out.println("枚举方法实现单例");
    }  
}
public class Test {

	public static void main(String[] args) {
		Singleton singleton = Singleton.INSTANCE;
		singleton.doSomeThing();//output:枚举方法实现单例

	}
}

这种方式也是《Effective Java 》以及《Java与模式》的作者推荐的方式,不过工作中却很少看到用。

3. 性能测试

五种单例实现方式,在100个线程下,每个线程访问1千万次实例的用时.

Tables 实现方式 用时(毫秒)
1 饿汉式 13
2 懒汉式 10778
3 双重检查 15
4 静态内部类 14
5 枚举 12

(*注意:由于不同电脑之间的性能差异,测试的结果可能不同)

4. 总结

根据不同场合选择具体的实现方式,一般情况下我是使用的静态内部类方式。

参考

单例模式性能分析

猜你喜欢

转载自blog.csdn.net/java_1996/article/details/85499888