您的位置:首页 > 其它

设计模式学习--前言与单例模式

2017-09-13 20:01 225 查看

前言

距离上次写博客已经快半年了,要么忙要么懒导致到现在才更新。相比于前半年来说技术增长了很多,进步就是看到以前写的代码你会觉得傻X才会这么写。这半年的时间中接触了更多的东西,安卓学习到了更多的框架架构,也尝试了兴起的kotlin,web方面进入了springboot与springcloud微服务的开发,负责整个项目从零到有进步很快,并且为了加快前台开发进度学习了nodejs,网站也即将上线。

对于微服务这一块的感慨很多,很想记录关于微服务学习的心得。但是看了很多大牛的文章或者经验心得,更想将java提升到一个更高的理解层面,去深入学习java的二十多种设计模式,jvm以及如何将代码写的更优雅。

设计模式的学习来自于作者 zuoxiaolong8810(左潇龙),从他的文章中学习到很多东西,自己也想做些简单的总结,但不会像他的那么详细。

单例模式

1.什么是单例模式

单例模式就是在应用中某一个类从始至终保持只有一个实例的状态。这种类的特征就是无论实例多少个他们都是相同的,保持单例的状态可以减少内存的消耗或者避免一些不可预料的错误产生。

2.如何实现单例模式

那么如何避免在实例化类是保持单例的特征呢,首先我们不能使用构造器方法创建实例,并且考虑使用静态方法来控制单例的创建。这边总结了四种单例模式,从存在缺陷到基本完美。

第一种,普通单例模式

/**
* 简单的单例模式 含并发的缺陷
* @Title: SingletonEasy.java
* @Package singleton
* @author 黄皓铨
* @date 2017-9-13 下午2:23:49
*/
public class SingletonEasy {

private static SingletonEasy singletonEasy;

//构造器私有化 防止外部直接初始化
private SingletonEasy(){};

public static SingletonEasy getInstance(){
if(singletonEasy == null){//如果不存在该实例,则创建。存在则直接返回该实例
singletonEasy = new SingletonEasy();
}
return singletonEasy;
}

}


这是第一种单例的模式,十分的简单。首先是需要私有化构造器,防止在外部通过构造器实例化类。静态方法getInstance中如果实例singletonEasy为空时,则创建新的类实例否则返回存在的实例,从而避免了类的重复创建。很容易理解但存在的缺陷在于并发问题。如果当外部成千上万的地方同时获取该类的实例时,在实例未完全创建完成前,另一个命令也因为实例为空的原因进入了if语句内,则会发生创建了新实例的情况,从而导致单例状态的失败。所以这种方法存在并发的缺陷。

第二种,稍微改进的单例模式

相对于上面的问题也有解决的办法,就是加入synchronized 同步,不让命令异步执行,缺点是增加了消耗的时间,所以我们不能每次命令都进行同步,应当在判断实例为空时同步,后面确定已存在实例后则不需同步了。代码如下:

/**
*  稍微改进的单例模式
* @Title: SingletonEasy2.java
* @Package singleton
* @author 黄皓铨
* @date 2017-9-13 下午2:36:57
*/
public class SingletonEasy2 {

private static SingletonEasy2 singletonEasy2;

//私有化构造函数
private SingletonEasy2(){}

//给出一个公共的静态方法返回一个单一实例
public static SingletonEasy2 getInstance(){
if (singletonEasy2 == null) {
synchronized (SingletonEasy2.class) {
if (singletonEasy2 == null) {
singletonEasy2 = new SingletonEasy2();
}
}
}
return singletonEasy2;
}
}


但这种方式仍然存在缺陷,与上面第一种类似。在jvm的层面,类的实例不是原子性的操作,他也分为三步,那就存在三步未执行完另一个命令开始创建实例的情况。

第三种,最好的单例模式

上面说到的jvm创建实例的三步,分别为 1.分配内存 2.初始化构造器 3.将对象指向分配的内存的地址,我们只要保证对象不要首先指向分配的内存就能保证单例的状态了。这边介绍第三种的单例的模式,运用内部类的方法:

/**
* 最终版的单例模式 解决并发问题减少内存的浪费
* @Title: SingletonFinal.java
* @Package singleton
* @author 黄皓铨
* @date 2017-9-13 下午2:29:40
*/
public class SingletonFinal {

//构造器私有化 防止外部直接初始化
private SingletonFinal(){};

private static SingletonFinal getInstance(){
//当需要使用到当前实例时再调用内部类创建实例,不会造成内存的浪费
return SingletonCreate.singleton;
}

//内部类创建唯一的实例
private static class SingletonCreate{
static SingletonFinal singleton = new SingletonFinal();
}
}


我们在类中创建了一个内部类SingletonCreate,里面含一个静态变量为初始化的类实例。外部getInstance方法直接获取内部类中初始化好的实例即可。这种方法能保持单例状态的原因是一个类的静态属性只会在第一次加载类时初始化,另外由于静态变量只初始化一次,所以singleton仍然是单例的,所以不用担心重复创建的问题。这种就是最好的单例模式。

第四种,懒人单例模式

懒人单例的模式与第三种的想法类似,如下:

/**
* 懒人单例模式,无并发问题,存在内存浪费的风险
* @Title: SingletonLazy.java
* @Package singleton
* @author 黄皓铨
* @date 2017-9-13 下午2:32:55
*/
public class SingletonLazy {

//类中直接声明创建静态实例,直接避免实例重复
private static SingletonLazy singleton = new SingletonLazy();

//构造器私有化 防止外部直接初始化
private SingletonLazy(){};

private static SingletonLazy getInstance(){
return singleton;
}

}


可以看到未使用内部类而是直接创建了一个静态的实例,这种方法在日常使用时是没有问题的,缺点是存在内存的浪费,因为这个类可能存在其他的静态区域,当我们相用到其他的静态资源时而不需要次类实例时,也会创建这个类的实例,导致了资源的浪费,而第三种方法中我们是使用了getInstance方法后才去访问内部类并创建实例的。

这就是总结的几种常见的单例模式,学习的文章中还提到了volatile相关的单例方法这里就不做介绍了。下一篇的内容应该为 代理模式 。代理模式常见的体现就是面向切面开发也就是AOP。学习的文章中用了一个数据库连接池的例子,讲的很好。希望可以想到其他好的例子,保持更新的进度。

下一篇 代理模式
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息