单例模式——皇帝XXX

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

皇帝类:

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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值