猴王出世
盘古开天辟地之后,天下分为四大部洲,在东胜神州傲来国的海边上有一个花果山,此山风景秀丽,美不胜收,又多桃树,遂成了猕猴的天堂。
花果山上有一块仙石,此石自天地开始便吸收天地灵气,慢慢周身生成360窍,开始呼吸,好像婴儿一样。终于有一天能量充满,从石头里蹦出来个灵胎,见风化为一个石猴,出来之后,眼里冒出两道神光,先拜天地,再拜四方,惊动的天上的玉皇大帝,之后石猴喝了人间的河水,把灵气压住,成了一个普通的石猴。石猴性格洒脱、好动、很快和猕猴们打成一片,吃饭、玩耍、睡觉都在一起,过着快快乐乐的生活。
故事梗概
有一座山,山上面有一块仙石,仙头里面有个灵胎,灵胎出世,变成石猴;
山上有很多桃树和猕猴群。
石猴加入到了猕猴群。
代码模拟
由于整个模拟过程,代码较多,请点击 http://download.youkuaiyun.com/detail/myhc2014/9194253 下载,代码中更加清晰的了解猴王出世故事的流程及代码模拟,也能清楚的了解到singleton的全部内容,里面还有其他模式哦。
关键代码
爱看西游的人都知道,整个西游中,一共有多少个孙悟空啊?
你的回答可能是这样的:
只有一个,天上地下只有一个……
实际上,大家想表达的是只有一个孙悟空。
大家都知道,在面向对象的语言中,一切都是对象,想生成一个对象,只要通过构造方法new一下即可,
那么,在代码中,如何保证只有一个孙悟空?
我是这么做到的:
public class StoneMonkey extends Lingtai implements IMonkey {
/**
* 花果山仙石内的灵胎所变的石猴,天地间只有一个
*/
private static StoneMonkey stoneMonkey;
private StoneMonkey(List<LingQi> lingQis) {
super();
System.out.println("石猴孕育完成");
}
/**
* 单例
*
* @return
*/
public static synchronized StoneMonkey getStoneMonkey(List<LingQi> lingQis) {
if (stoneMonkey == null) {
stoneMonkey = new StoneMonkey(lingQis);
}
return stoneMonkey;
}
您可能感觉没啥啊,这个看着没什么的代码,就能实现?
这个真的能实现,具体原因听我道来(采用苏格拉底诱导问答)。
问 | 答 |
---|---|
构造方法private,外面能new对象吗? | 不能 |
外面不能new对象,那么类内部能访问private构造方法吗? | 能,但是即使创建了对象,外面也无法访问啊 |
如果通过对象调用的方式确实无法访问,但是别忘了,还有可以通过类来调用的方法哦 | 哦,是的,静态方法的确可以通过类直接进行访问 |
那么通过静态方法,调用能否解决私有构造方法类对象的创建问题? | 恩,可以解决的 |
这样就完了吗? | 应该可以了 |
是的,在单线程的应用中是可以了,但在多线程并发下呢? | … |
synchronized/ lock 可以帮到你 |
好了,通过上面的对话式描述,对上文的代码是不是更清晰了?
其实啊,我用了一个非常简单的模式来实现这个功能——singleton。
singleton? 听着挺高级的,他到底是个什么东西?
让我们来一步一步的揭开他的神秘面纱
singleton
- 试用场景
当某个事情是独一无二的,整个代码运行环境中,不会在出现第二个对象的时候(如天上地下就一个孙悟空),就可以考虑使用singleton。 - 代码特点
2.1. 构造方法私有
只有构造方法是私有的情况下,其他类才不能直接调用该类的构造方法来创建对象,此条件是单例的基础。
2.2 开放静态获取类实例方法
在没有该类对象的情况下,为了让其他类能正常的获取到该类的对象,所以需要写一个public static 的方法,用于返回该类的对象/实例。
2.3 私有静态对象实例
为了保证静态方法能获取到实例对象,所需需要一个静态实例;
为了保证,外界只能通过开放的静态同步方法获取该实例,所以必须为private。 - 标准类图
后续补上 - 标准实现代码
singleton共有两种写法,饿汉式 和 懒汉式 singleton,懒汉式还可以进化为 Double-checked Locking ,上文中的代码仅仅是懒汉式singleton。
4.1 懒汉式:
最常用的一种实现方案
public class StoneMonkey extends Lingtai implements IMonkey {
/**
* 花果山仙石内的灵胎所变的石猴,天地间只有一个
*/
private static StoneMonkey stoneMonkey;
private StoneMonkey(List<LingQi> lingQis) {
super();
System.out.println("石猴孕育完成");
}
/**
* 单例
*
* @return
*/
public static synchronized StoneMonkey getStoneMonkey(List<LingQi> lingQis) {
if (stoneMonkey == null) {
stoneMonkey = new StoneMonkey(lingQis);
}
return stoneMonkey;
}
有些朋友可能会问,为啥在getStoneMonkey前加了一个synchronized ?
因为在多线程的应用里,不加 synchronized 可能会出现问题。
示例如下:
假设对 getStoneMonkey()方法的两个调用几乎同时发生,这时候会发生什么?
1. 第一个线程检测实例是否存在,因为实例不存在,该线程执行创建第一个实例的代码部分(new StoneMonkey(lingQis))。
2. 然而,假设在实例化完成之前,另一个线程也来检测实例成员变量是否为null。因为第一个线程还什么都没有创建,实例成员变量仍然等于null,所以第二个线程也执行了创建一个对象的代码(new StoneMonkey(lingQis))。
3. 现在,两个线程都执行了 singleton对象的new操作,因此创建了两个对象。
影响可想而知(出现了多个孙悟空)!
为了解决这个问题,可以对整个获取实例对象方法加锁(synchronized)。
懒汉式构造对象特点:
在其他类调用“get实例对象方法”(如getStoneMonkey(List lingQis)) 的时候,在创建对象。
优点: 由于在需要的时候,才实例化对象,故在不需要使用对象的时候,无没有必要的内存占用(相对于饿汉式)。
缺点: 由于在需要的时候,才实例化对象,故获“get实例对象方法”耗时长
4.2 Double-checked Locking
Double-checked Locking 只适用于多线程程序,如果在单线程的情况下,Double-checked Locking 是没有意义的(根本就不需要考虑多线程问题)。
鉴于懒汉式的创建方法中,synchronized 锁定了整个方法,而其他线程需要获取该对象时,必须等待。这将导致一个瓶颈。
为了解决这个瓶颈问题,采用双重校验的方式进行相应的解决。
public class StoneMonkey2 extends Lingtai implements IMonkey {
/**
* 花果山仙石内的灵胎所变的石猴,天地间只有一个
*/
private static StoneMonkey2 stoneMonkey;
private StoneMonkey2(List<LingQi> lingQis) {
super();
System.out.println("石猴孕育完成");
}
/**
* 双重检测加锁
*
* @return
*/
public static StoneMonkey2 getStoneMonkey(List<LingQi> lingQis) {
if (stoneMonkey == null) {
synchronized (StoneMonkey2.class) {
if (stoneMonkey == null) {
stoneMonkey = new StoneMonkey2(lingQis);
}
}
}
return stoneMonkey;
}
特点: 在创建对象前,添加一次检查,避免不必要的锁定
4.3 饿汉式
public class StoneMonkey3 extends Lingtai implements IMonkey {
/**
* 花果山仙石内的灵胎所变的石猴,天地间只有一个
*/
private static StoneMonkey3 stoneMonkey = new StoneMonkey3();;
private StoneMonkey3() {
super();
System.out.println("石猴孕育完成,没有吸收灵气");
}
/**
* 饿汉式
*
* @return
*/
public static StoneMonkey3 getStoneMonkey(List<LingQi> lingQis) {
return stoneMonkey;
}
特点:在类被虚拟机加载时就自动调用构造方法(StoneMonkey3())生成实例对象了。
优点: 由于类被加载的时候,就创建了实例,故外面获取实例对象时,耗时短。
缺点:由于类被加载的时候,就创建了实例,故可能会造成无用的内存占用。
总结:
单例模式是非常非常简单的一个模式,其两种实现方式也是比较简单的,至于平时的使用,建议大家多考虑一下场景,也多想一下孙悟空。
本人技术有限,如果有说不对的地方,欢迎随时指正。
非常欢迎大家留言交流,让我们共同进步~