喵星之旅-沉睡的猫咪-单例模式
一、单例是什么?
单例是gof所提到的23种设计模式中的一种,属于创建型设计模式。
1、结构:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
2、动机:对于某些类,只有一个实例是很重要的。尤其是某些具有高级权限的对象,比如线程池可以启动线程,这是对cpu这种极其昂贵资源的调用,单例显得尤为重要。
3、效果:对唯一实例的受控访问。
4、三要素:私有的构造方法;指向自己实例的私有静态引用;以自己实例为返回值的静态的公有方法。
二、饿汉式单例
在类加载初始化的时候就主动创建实例。单例不要实现序列化接口,这里只是为后面错误演示提供方便。
package create.singleton.hungry; import java.io.Serializable; /** * * @author bunny~~我是兔子我会喵,我叫喵星兔。 * 饿汉单例 HungrySingleton */ public class EhanDanli implements Serializable { private static final long serialVersionUID = 1L; private static final EhanDanli instance = new EhanDanli(); private EhanDanli() { } public static EhanDanli getInstance() { return instance; } }
package create.singleton.hungry; /** * * @author bunny~~我是兔子我会喵,我叫喵星兔。 * */ public class Test { public static void main(String[] args) { EhanDanli s1 = EhanDanli.getInstance(); EhanDanli s2 = EhanDanli.getInstance(); EhanDanli s3 = EhanDanli.getInstance(); System.out.println(s1); System.out.println(s2); System.out.println(s3); System.out.println(s1 == s2); } }
三、懒汉式单例
等到真正使用的时候才去创建实例,不用时不去主动创建。
package create.singleton.lazy; /** * * @author bunny~~我是兔子我会喵,我叫喵星兔。 * 懒汉单例 LazySingleton */ public class LanhanDanli { private LanhanDanli() { } private static LanhanDanli instance; public static LanhanDanli getInstance() { if (instance == null) { synchronized (LanhanDanli.class) { if (instance == null) { instance = new LanhanDanli(); } } } return instance; } }
package create.singleton.lazy; /** * * @author bunny~~我是兔子我会喵,我叫喵星兔。 * */ public class Test { public static void main(String[] args) { LanhanDanli s1 = LanhanDanli.getInstance(); LanhanDanli s2 = LanhanDanli.getInstance(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } }
四、枚举单例
jvm
提供底层保证,不可能出现序列化、反射产生对象的漏洞 。
package create.singleton.register; /** * * @author bunny~~我是兔子我会喵,我叫喵星兔。 * EnumSingleton */ public enum ZhuceDanli { INSTANCE; public static ZhuceDanli getInstance() { return INSTANCE; } }
package create.singleton.register; public class Test { public static void main(String[] args) { ZhuceDanli s1 = ZhuceDanli.getInstance(); ZhuceDanli s2 = ZhuceDanli.getInstance(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } }
五、单例的破坏
1、反射
单例实现的一个主要因素就是构造器私有化,但是反射可以执行私有化的构造器。
package create.singleton; import java.lang.reflect.Constructor; import create.singleton.hungry.EhanDanli; /** * * @author bunny~~我是兔子我会喵,我叫喵星兔。 * Reflect 反射破坏 */ public class FanshePohuai { public static void main(String[] args) throws Exception { Class<?> clazz = EhanDanli.class; Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); Object s1 = c.newInstance(); Object s2 = c.newInstance(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } }
2、序列化、反序列化
破坏原因同上。
package create.singleton; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import create.singleton.hungry.EhanDanli; /** * * @author bunny~~我是兔子我会喵,我叫喵星兔。 *Seriable 序列化破坏 */ public class XunliehuaPohuai { public static void main(String[] args) throws Exception { EhanDanli s1 = EhanDanli.getInstance(); EhanDanli s2 = null; FileOutputStream fos = null; fos = new FileOutputStream("Seriable.bunny"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("Seriable.bunny"); ObjectInputStream ois = new ObjectInputStream(fis); s2 = (EhanDanli) ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } }
六、单例的总结和选择
饿汉式单例:执行效率高,性能高,启动慢。
懒汉式单例:需要考虑锁的问题,没有启动问题,相对难度大。
注册式单例:无法被破坏的单例。实际上就是饿汉式,只不过某些人通过给jdk的官方每人饭里加了鸡腿后,让他们创建了一个霸权模式的饿汉。
重要的对象优选饿汉式。因为重要的内容终究是不多的,没必要在意那些启动时间问题。
不重要的选择懒汉式或者不建议用单例,需要考虑是否必要,因为“单例是邪恶的”。
单例的选择是一把双刃剑,我们要考虑到世界上有一种叫“客户”的动物,外号“上帝”。
对于注册式单例,有赞成使用的,因为安全,不会被破坏。本人不赞成使用。
因为一个完善的生态需要有出生、成长、消亡的过程,而不是永存。健全的生态是平等而不是独裁。如果上帝创造了一种生物,不单没有天敌,而且不会死亡,那么他创造的就是恶魔、灾难,他要有消亡的过程。如果大权永远掌握在一个人手中,那是独裁,就像墨索里尼。
就像丘吉尔和斯大林的对话。斯大林问丘吉尔:“丘吉尔先生,您打赢了仗,人民却罢免了您。”丘吉尔回敬道:“斯大林先生,我打仗就是保卫让人民有罢免我的权力。
而注册式单例不允许他人去罢免他,终究不是我的选择,至于如何选择,仁者见仁。我更愿意追随我最喜欢的二战英雄。
单例到底带来了什么?不是对象的唯一性,最终是受控访问,也就是控制权限,和对象的边界有着很多的联系。如果我们在开发中考虑到了权限的限制,并且想通过对象数量来控制,那就是基于单例模式的,哪怕最后出现的是2个对象。单例模式不限于它具体的代码实现和教条式的定义,只要我们心中所想和它一样,哪怕实现方式不同,那也是单例的灵魂,可能不是单例的代码。设计模式不是让我们照搬他们的代码,而且起到抛砖引玉的目的。我们是通过学习他们的设计思想、处理问题的经验,然后提升改进我们自己的代码。
ps:项目地址 svn://47.105.188.20/kitty/2%E3%80%81code/pattern 用户名密码:reader/reader
作者:喵星兔
出处:https://www.cnblogs.com/kittybunny/
喵星之旅:https://www.cnblogs.com/kittybunny/p/12148641.html
我的视频:https://space.bilibili.com/518581788
更多内容:不咬人的小兔子
本博客所有文章仅用于学习、研究和交流目的,欢迎非商业性质转载。
我是兔子,我会喵,我叫喵星兔~~