单例模式——皇帝XXX

来源:互联网 发布:淘宝c店运营提成 编辑:程序博客网 时间:2024/05/01 01:05

秦王嬴政统一中国,认为自己“德兼三皇、功盖五帝”,创“皇帝”一词作为华夏最高统治者的正式称号。自此“皇帝”这个称号沿袭了两千多年。皇帝每天的任务是接待众多的臣子,而众多臣子每天我面对同一个皇帝。臣子们一提到皇帝,便知道是指谁了。因为皇帝只有一个嘛!
那么我们怎么在程序中把这种现象体现出来呢?没错,单例模式!那来看看具体怎么实现的吧:

皇帝类:

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拦截在外面了。
 ̄▽ ̄

2 0