秦王嬴政统一中国,认为自己“德兼三皇、功盖五帝”,创“皇帝”一词作为华夏最高统治者的正式称号。自此“皇帝”这个称号沿袭了两千多年。皇帝每天的任务是接待众多的臣子,而众多臣子每天我面对同一个皇帝。臣子们一提到皇帝,便知道是指谁了。因为皇帝只有一个嘛!
那么我们怎么在程序中把这种现象体现出来呢?没错,单例模式!那来看看具体怎么实现的吧:
皇帝类:
public class Emperor {
private static Emperor unique = null; //初始化一个皇帝
private Emperor() {
//可不允许冒充皇帝,只能有一个
}
public static Emperor getInstance() { //获取皇帝对象,该方法是唯一能获取皇帝对象的通道
if (unique == null) {
unique = new Emperor(); //国不可无君
}
return unique;
}
public String name() { //返回皇帝的名字xxx
return "xxx";
}
}
臣子类:
public class Minister {
int num; //臣子众多,给个编号吧
public Minister(int num) {
this.num = num;
}
public void answer(Emperor e) { //被问皇帝的名字,作出回答
System.out.println("臣子"+ num + ":皇帝的名字是" + e.name());
}
}
测试:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Emperor emperor = Emperor.getInstance(); //连续十次获取皇帝这个类的对象
Minister minister = new Minister(i + 1); //初始化十个臣子
minister.answer(emperor); //每个臣子说出皇帝的名字
}
}
}
那么,看看结果吧:
臣子1:皇帝的名字是xxx
臣子2:皇帝的名字是xxx
臣子3:皇帝的名字是xxx
臣子4:皇帝的名字是xxx
臣子5:皇帝的名字是xxx
臣子6:皇帝的名字是xxx
臣子7:皇帝的名字是xxx
臣子8:皇帝的名字是xxx
臣子9:皇帝的名字是xxx
臣子10:皇帝的名字是xxx
臣子们异口同声地说出了皇帝的名字。这就是单例模式!
当然,我们把皇帝类稍加改动,也可以实现相同的效果:
public class Emperor {
private static Emperor unique = new Emperor(); //在类初始化时直接创建一个皇帝对象
private Emperor() {
//可不允许冒充皇帝,只能有一个
}
public static Emperor getInstance() {
return unique; //直接返回创建好的对象
}
public String name() {
return "xxx";
}
}
这种方法称为饿汉式,顾名思义,就是饿汉急切地想要实物。所以在类初始化地时候就创建好一个对象,然后在getInstance()方法中直接返回。
而通过
if (unique == null) {
unique = new Emperor();
}
return unique;
该语句进行判断的称为懒汉式,即在需要的时候创建。两者区别在于饿汉式需要提前占用内存空间。而懒汉式则会面临多线程并发时的安全问题。
单例模式与线程安全
public static Emperor getInstance() {
if (unique == null) {
unique = new Emperor();
}
return unique;
}
依然是这条方法,想想,有两个线程同时调用了getInstance()方法,当第一个线程进入if的判断语句,unique == null,准备创建一个皇帝。但是unique = new Emperor()当这条语句尚未被线程1执行时,线程2也进入了if判断语句,此时unique依然是null,导致产生了两个皇帝 !一山不容二虎呀,肯定出了大问题。
这就是单例模式线程安全问题。我们可以通过同步加锁来解决:
初级版:
public static synchronized Emperor getInstance() { //在方法头加上synchronized 关键字,保证只有一个线程访问
if (unique == null) {
unique = new Emperor();
}
return unique;
}
可是,当我们没有必要每次调用这个方法时都要加锁,那么可以改造一下:
高级版:
public static Emperor getInstance() {
synchronized (Emperor.class) {
if (unique == null) {
unique = new Emperor();
}
}
return unique;
}
在这里,我们看到我们只在方法内部需要的部分加上了锁,可是还有个疑问,如果我们第二次调用该方法时,就直接返回已经创建好的对象,再加锁就没有意义,反而消耗系统资源。
终极版(双重锁定):
public static Emperor getInstance() {
if (unique == null) { //判断皇帝是否创建,没有的话才加锁
synchronized (Emperor.class) {
if (unique == null) {
unique = new Emperor();
}
}
}
return unique;
}
可能,会有这样的疑问,在第一个if语句都已经判断对象为空了,为什么加锁后还要判断一次?原因在于,如果两个同时进程进入第一个判断语句,线程1加锁,线程2等待,当线程一完成创建完成后(此时对象不为空),线程2加锁进入就不能再次创建对象,而第二个if判断就把线程2拦截在外面了。
 ̄▽ ̄