大家好,又见面了,我是全栈君。
单例模式—不要冒充我!
我就是我 是颜色不一样的烟火
天空开阔 要做最坚强的泡沫。——《我就是我》-张国荣
有人冒充我
给大家说一个秘密了,其实我和设计模式本来并不认识,是相识于网络上,我们聊的很多,聊人生聊梦想,有一天我突然说,设计模式我们一起去旅行吧,她说可以啊!所以才有着一次的旅行。
但是总有一些人想要冒充我,因为他们看到了我和设计模式的这场旅行,那么怎么保证“设计模式”一定是和我一起旅行呢?
这个我在真实的世界只有一个(在目前看还是,以后科技发展那谁知道呢),但是在计算机的世界里,在Java的世界的,可以通过new 产生多个和我一样表现的对象,那么设计模式怎么知道那个是真正的我呢,假如她和其他人去旅行了,那我策划的这一切不就泡汤了吗!(在Java的世界里通过new 的确生成了许多个一样的对象,但是真实的情况每个对象在内存空间中有自己唯一的地址。)
所以我保证不管怎么冒充我(进行实例化),我都能只是一个,对应计算机内存空间只有一个唯一的我。
如下例子:
/** * 总有刁民想冒充朕! * * @author:dufyun * @version:1.0.0 * @date 2018/5/20 */
public class ManyMeTest {
public static void main(String[] args) {
Pattern pattern = new Pattern();
pattern.setName("波涛汹涌的PatternMM");
//真实的我
Me me = new Me();
me.setName("dufyun");
//我们约定在520这一天乘坐飞机出发
me.setTravelTime("2018-05-20 12:00:00");
//假冒我的人,冒牌
Me fakeMe = new Me();
fakeMe.setName("fakeDufyun");
fakeMe.setTravelTime("2018-05-20 10:00:00");
//假冒的我先带着MM走了
fakeMe.travel(pattern);
//到了12点我去飞机场等MM,发现MM离开了!我有点忧伤!
me.travel(pattern);
/* fakeDufyun带着,波涛汹涌的PatternMM在2018-05-20 10:00:00出发了! MM已经离开了! */
}
static class Me{
private String name;
private String travelTime;
private static Boolean state = true;
//省略get
public void setName(String name) {
this.name = name;
}
//省略get
public void setTravelTime(String travelTime) {
this.travelTime = travelTime;
}
public void travel(Pattern pattern) {
if(state){
state = false;
System.out.println(this.name + "带着," + pattern.getName() + "在" + this.travelTime + "出发了!");
}else{
System.out.println("MM已经离开了!");
}
}
}
static class Pattern{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
上面这个例子的话就不是Me就不是单例了,那么这时候就出问题了。设计模式MM和假冒的我去旅行了。这个例子不是特别准确我为了好理解简单写的,不要深究!
要让一个类不能new 出多的实例,首先设置它的构造方法为私有的(限制外部通过new创建实例),这样在类的内部提供一个可以供外部获取实例的方法,在这个方法中进行实例的初始化,或者在类的加载的时候就初始化类的实例,并通过方法去获取实例!不管哪种方式实现,我们都一定要考虑安全性和性能!
扩展:单例有什么好处呢?
- ①上面我们通过一个简单的例子,说明了如果Me不是单例的话,就会有问题,对应在程序中的话就会出现异常情况,导致程序不正常运行!这是不容许的!
- ②比如Windows的任务管理,菜单上多次点击“启动任务管理器”。只能启动一个!如果启动多个会有什么问题呢?(多个窗口显示不一样,容易误导;重复打开也会消耗系统资源!)
- ③有些对象我们只要一个,如线程池、缓存…
总结:一、多例会出现问题!二、多例浪费系统资源!
下面进入单例模式的讲解!
单例模式
概念
单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局的访问点。(有些书称为单件模式,单态模式)
- 某个类自己管理一个单独的实例,同时避免其他类在自行产生实例!
- 提供这个实例的全局访问点,需要实例的时候,问这个类要,它会返回单个实例。
简单点说:这个类只能有一个实例,这个实例必须它自己进行创建,然后向整个系统提供这个实例!
类图
下面是单例模式的类图,单例模式的类图是比较简单的,只有一个类!
实现方式
下面一些单例模式的实现方式,感谢那些前辈,能够设计出越来越好的单例模式!
饿汉模式实例单例 – 线程安全
/** * 饿汉模式实例单例 * * @author:dufyun * @version:1.0.0 * @date 2018/5/20 */
public class HungrySingleton {
private static HungrySingleton singleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return singleton;
}
public static void main(String[] args) {
HungrySingleton hug1 = HungrySingleton.getInstance();
HungrySingleton hug2 = HungrySingleton.getInstance();
System.out.println(hug1 == hug2); //true
}
}
懒汉模式 实例单例 – 线程不安全
/** * 懒汉模式实现单例模式 * * @author:dufyun * @version:1.0.0 * @date 2018/5/20 */
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
private static LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
public static void main(String[] args) {
LazySingleton la1 = LazySingleton.getInstance();
LazySingleton la2 = LazySingleton.getInstance();
System.out.println(la1 == la2); //true
}
}
总结一下饿汉和懒汉实现
懒汉模式其实比饿汉模式更好一点,是需要的时候才初始化实例!
其中请记住,在时间和效率上,我们经常用的无非是时间换空间或者空间换时间!
但是这两种方式实现目前看是没有问题,但是在多线程的情况下就会出现问题!
于是我们有了下面的实现方式。
/** * 同步方法实现单例模式 - 线程安全 * * @author:dufyun * @version:1.0.0 * @date 2018/5/20 * @update:[日期YYYY-MM-DD] [更改人姓名][变更描述] */
public class SynchMethodSingleton {
private static SynchMethodSingleton instance;
private SynchMethodSingleton(){}
public static synchronized SynchMethodSingleton getInstance(){
if(instance == null){
instance = new SynchMethodSingleton();
}
return instance;
}
static class MyThread extends Thread{
@Override
public void run() {
System.out.println(SynchMethodSingleton.getInstance().hashCode());
}
public static void main(String[] args) {
MyThread[] sp = new MyThread[10];
for (int i = 0; i < sp.length; i++) {
sp[i] = new MyThread();
}
for (int i = 0; i < sp.length; i++) {
sp[i].start();
}
}
}
}
这种方虽能解决多线程问题,但是在每次调用此方法的时候都要同步,在多线程时候回影响效率!所以我们需要进行改善!
双重检查加锁实例单例
/** * 双重检查锁实现单例模式 * * @author:dufyun * @version:1.0.0 * @date 2018/5/20 */
public class DoubleCheckLockSingleton {
private static volatile DoubleCheckLockSingleton instance = null;
private DoubleCheckLockSingleton() {}
/** * 双重检查锁 */
public static DoubleCheckLockSingleton getInstance(){
if(instance == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//锁的是代码块。效率更高
synchronized (DoubleCheckLockSingleton.class) {
// 为什么要进行二次检查呢,因为当线程创建实例对象并 出锁的代码块,新的线程进入后不会再次创建实例对象,保证对象是单例的
//二次检查
if(instance == null){
instance = new DoubleCheckLockSingleton();
}
}
}
return instance;
}
static class MyThread extends Thread{
@Override
public void run() {
System.out.println(DoubleCheckLockSingleton.getInstance().hashCode());
}
public static void main(String[] args) {
MyThread[] sp = new MyThread[200];
for (int i = 0; i < sp.length; i++) {
sp[i] = new MyThread();
}
for (int i = 0; i < sp.length; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sp[i].start();
}
}
}
}
使用双重检查的两个注意点:
– ① volatile 关键字,内存可见性、禁止指令重排序
– ② 使用synchronized 进行同步
注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java5及以上的版本。
静态(static)内部类实例单例 – 线程安全
静态内部类方式可以实现延迟加载,又可以保证线程安全,不影响系统性能,是比较好的Java语言单例模式实现方式!
采用静态初始化器的方式,由JVM来保证线程的安全性!
/** * 静态内部类实现单例模式 * * 这种模式在 序列化与反序列化中会使单例模式失效。序列号和反序列话的对象不是同一个 * @author:dufyun * @version:1.0.0 * @date 2018/5/20 */
public class StaticInnerClassSingleton {
private static class SingletonPatternHolder{
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
private StaticInnerClassSingleton() {}
public static StaticInnerClassSingleton getInstance(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return SingletonPatternHolder.instance;
}
static class MyThread extends Thread{
@Override
public void run() {
System.out.println(StaticInnerClassSingleton.getInstance().hashCode());
}
public static void main(String[] args) {
MyThread[] sp = new MyThread[10];
for (int i = 0; i < sp.length; i++) {
sp[i] = new MyThread();
}
for (int i = 0; i < sp.length; i++) {
sp[i].start();
}
}
}
}
枚举实现单例 – 线程安全
用枚举来实现单例非常简单,使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。
/** * 枚举实例单例模式 * * @author:dufyun * @version:1.0.0 * @date 2018/5/20 */
public enum EnumSingleton {
instance;
public static EnumSingleton getInstance(){
return instance;
}
static class MyThread extends Thread{
@Override
public void run() {
System.out.println(EnumSingleton.getInstance().hashCode());
}
public static void main(String[] args) {
MyThread[] sp = new MyThread[10];
for (int i = 0; i < sp.length; i++) {
sp[i] = new MyThread();
}
for (int i = 0; i < sp.length; i++) {
sp[i].start();
}
}
}
}
总结
最后,不管采取何种方案,请时刻牢记单例的三大要点:
- 线程安全 (重要)
- 延迟加载,性能问题 (重要)
- 序列化与反序列化安全
注意:
– 单例的实现是在同一个类加载器的作用下完成的,如果使用多个类加载器,那么生成的实例就不是单例!会导致单例失效!
– 双重检查需要注意使用JDK1.5以上版本
?Think? : 通过上面一系列的方法,可以解决被人冒充我和我的设计模式去旅行,那么你可以思考一下改进我最开始的代码,让真实的Me去和设计模式MM去飞机场,而不一个冒牌的我带走了MM!
Next :下一次要考虑旅途中的钱的问题了,请期待下一篇!
参考
- 你真的会写单例模式吗——Java实现
- 单例模式的优缺点
- (一)单例模式详解
- 史上最全设计模式导学
- 《图解设计模式》
- 《Head First 设计模式》
本专栏文章列表
一、设计模式-开篇—为什么我要去旅行? #和设计模式一起旅行#
二、设计模式-必要的基础知识—旅行前的准备 #和设计模式一起旅行#
三、设计模式介绍—她是谁,我们要去哪里? #和设计模式一起旅行#
四、单例模式—不要冒充我,我只有一个! #和设计模式一起旅行#
五、工厂模式—旅行的钱怎么来 #和设计模式一起旅行#
六、策略模式—旅行的交通工具 #和设计模式一起旅行#
七、观察者模式——关注我,分享旅途最浪漫的瞬间! #和设计模式一起旅行#
八、装饰者模式—巴厘岛,奶茶店的困扰! #和设计模式一起旅行#
九、命令模式—使用命令控制奶茶店中酷炫的灯 #和设计模式一起旅行#
十、模板方法模式—制作更多好喝的饮品! #和设计模式一起旅行#
十一、代理模式 —专注,做最好的自己!#和设计模式一起旅行#
十二、适配器模式——解决充电的烦恼 #和设计模式一起旅行#
十三、外观模式—— 简化接口 #和设计模式一起旅行#
十四、迭代器模式—— 一个一个的遍历 #和设计模式一起旅行#
十五、组合模式—— 容器与内容的一致性 #和设计模式一起旅行#
十六、状态模式—用类表示状态 #和设计模式一起旅行#
十七、访问者模式-访问数据结构并处理数据 #和设计模式一起旅行#
十八、职责链模式-推卸责任,不关我的事,我不管!#和设计模式一起旅行#
十九、原型模式—通过复制生产实例 #和设计模式一起旅行#
二十、设计模式总结—后会有期 #和设计模式一起旅行#
如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!
如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!
欢迎访问我的csdn博客,我们一同成长!
不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!
博客首页 : http://blog.csdn.net/u010648555
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/121102.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...