本文主要介绍了设计模式的六大原则,并结合实例描述了各种单例模式的具体实现和性能分析测试。包括:饿汉式
、静态内部类
、懒汉式
、双重校验锁
、枚举
等。
更多Java设计模式系列文章,欢迎访问我的个人博客–>幻境云图
1. 设计模式的六大原则
1、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。
2、里氏代换原则(Liskov Substitution Principle)
其官方描述比较抽象,可自行百度。实际上可以这样理解:
(1)子类的能力必须大于等于父类,即父类可以使用的方法,子类都可以使用。
(2)返回值也是同样的道理。假设一个父类方法返回一个List,子类返回一个ArrayList,这当然可以。如果父类方法返回一个ArrayList,子类返回一个List,就说不通了。这里子类返回值的能力是比父类小的。
(3)还有抛出异常的情况。任何子类方法可以声明抛出父类方法声明异常的子类。而不能声明抛出父类没有声明的异常。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。
扫描二维码关注公众号,回复: 5128863 查看本文章
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. 总结
根据不同场合选择具体的实现方式,一般情况下我是使用的静态内部类方式。