上一篇《Java设计模式之开篇》介绍了设计的六大原则,分别是,单一职责、里氏替换原则、依赖倒置、迪米特法则、接口隔离、开闭原则。每个原则都经过定义解释和代码实战进行详细体现,最后也总结了这六大原则,原则是死的,人是活的,咱们要根据实际状况是使用六大原则,不要生搬硬套,为了原则而原则,为了模式而模式。这一篇,咱们来介绍下设计模式最简单的一个模式,单例模式。java
单例模式,英文:Singleton Pattern,英文解释:Ensure a class has only instance,and provide a global point of access to it.翻译过来就是说,要确保一个类只有一个实例,并且自行实例化而且向整个系统提供这个实例。数据库
单例模式的使用场景要求能够用一句话表示,若是一个类有多个对象会致使系统可能出现问题就要采用单例模式,通常的场景以下:编程
a.建立一个对象须要耗费大量资源或者时间,如IO,数据库链接等。设计模式
b.生成惟一id状况。安全
c.工具类,通常就能够采用静态类能够知足。bash
咱们来设计一个单例模式,场景:中国古代,通常状况,某一段时间只能有一个皇帝,固然特殊状况除外,不管是大臣仍是平民,求见的皇帝都是同一个,咱们用代码实现这个场景多线程
//皇帝接口
public interface Iemperor {
//皇帝下命令
public void sayCommand(String str);
}
//明朝皇帝实现类
public class MingEmperor implements Iemperor {
private static MingEmperor emperor=new MingEmperor(new Random().nextInt(10)+"");
//皇帝身份id
private String id;
//防止破坏单例
private MingEmperor(String id) {
this.id = id;
}
@Override
public void sayCommand(String str) {
System.out.println(str+"----------我是皇帝,这是个人id="+id);
}
public static MingEmperor getEmperor(){
return emperor;
}
}
//场景客户类
public class Client {
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
MingEmperor.getEmperor().sayCommand("求见皇帝");
}
}
}
复制代码
执行下场景类,输出结果为dom
public class MingEmperor implements Iemperor {
private static MingEmperor emperor;
//皇帝身份id
private String id;
//防止破坏单例
private MingEmperor(String id) {
this.id = id;
}
@Override
public void sayCommand(String str) {
System.out.println(str+"----------我是皇帝,这是个人id="+id);
}
public static MingEmperor getEmperor(){
if (emperor==null){
emperor= new MingEmperor(new Random().nextInt(10)+"");
}
return emperor;
}
}
复制代码
这里获取皇帝对象的时候,判断是否为空,若是为空就new一个对象,不然直接返回以前实例化过的对象。咱们按照原来的场景类运行下,结果以下:ide
这和咱们预期结果同样,难道这样就ok了吗?既然是单例的,那么多线程环境下确定也是单例的,咱们换成多线程试试。工具
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
MingEmperor.getEmperor().sayCommand("求见皇帝");
}
}).start();
}
}
复制代码
执行结果:
public class MingEmperor implements Iemperor {
//增长volatile修饰,防止虚拟机指令重排序
private static volatile MingEmperor emperor;
//皇帝身份id
private String id;
//防止破坏单例
private MingEmperor(String id) {
this.id = id;
}
@Override
public void sayCommand(String str) {
System.out.println(str+"----------我是皇帝,这是个人id="+id);
}
public static synchronized MingEmperor getEmperor(){
if (emperor==null){
//采用同步代码块,缩小锁定范围,比直接同步方法效率要略高
synchronized (MingEmperor.class){
//这里再判断为空,是防止别的线程已经完成了实例化,这里重复实例化了,就违反了单例。
if (emperor==null) emperor= new MingEmperor(new Random().nextInt(10)+"");
}
}
return emperor;
}
}
复制代码
以上代码改造,主增长了volatile修饰全局变量,该变量主要功能就是增长线程之间的可见性,同时防止指令重排序(关于volatile变量,后续我会出一片文章详细说)。另外缩小了synchronized的范围,采用同步代码块。这样就完成了线程安全的懒汉式单例模式,该写法被称为,双重检查锁定(DCL)。
其实咱们结合上一篇的知识,再看看这部分代码,发现这个单例模式违反了一个原则,就是单一职责原则,按照单一职责原则,皇帝类不用关心什么单例不单例,我只要传达命令就行了。因此这个模式也告诉了你们,原则要灵活使用。
1.刚刚上面提到的,单例模式违反了单一职责。
2.单例模式严格意义上说是没有接口的,要扩展只能修改,虽然上面的例子实现了接口,可是并不能针对接口作一个单例模式,由于单例模式要求“自行实例化”,接口和抽象类是不能被实例化的。因此在每一个实现类进行单例模式,就算实现了接口,每一个实现类都要本身实现一套单例的逻辑,也就是形成了代码重复。
单例模式主要优势就算减小了系统资源消耗,优化了系统性能。
其实,咱们用到的池化技术能够理解为单例模式的一种扩展,池化技术就是能够容许建立指定数量的实例,而单例模式就至关于池化数量为1 。因此,模式在于要消化理解,而后灵活变通使用。另外须要注意的是,咱们在设计单例模式的时候还须要考虑到一种破坏单例模式的状况,就是克隆方式,虽然咱们私有化了构造方法,可是克隆对象并不须要执行构造方法,因此这里也是一个潜在破坏单例模式的方式。解决方法就是单例类不要实现Cloneable接口。
《设计模式之禅》