菜鸟成长系列-概述
菜鸟成长系列-面向对象的四大基础特性
菜鸟成长系列-多态、接口和抽象类
菜鸟成长系列-面向对象的6种设计原则
java
前面已经将设计模式中的基本内容撸了一下,今天开始正式开始设计模式系列的内容,由于网上也有不少关于设计模式的技术博客,从不一样的角度对设计模式都作了很详细的解读;本系列的模式除了基本的概念和模型以外,还会结合java自身使用的和Spring中使用的一些案例来进行学习分析。
水平有限,若是存在不当之处,但愿你们多提意见,灰常感谢!
设计模式中整体分为三类:
1、建立型(5):spring
还有一个简单工厂[Simple Factory],目前有两种,有的把单例模式做为这5种之一,有的是将简单工厂做为这5种之一。这里不作讨论,原则上两个都是,只是划分规则不一样。设计模式
2、结构型(7)缓存
3、行为型(11)安全
首先它是一种建立型模式,与其余模式区别在于:单例模式确保被建立的类只有一个实例对象,并且自行实例化并向整个系统提供这个实例。通常状况下咱们称当前这个类为单例类。
bash
从上面这段话中咱们能够了解到,单例模式具有如下三个要点:app
OK,来看单例模式的几种实现方式。函数
方式一:饿汉式源码分析
package com.glmapper.design.singleton;
/**
* 单例模式-饿汉式
* @author glmapper
* @date 2017年12月17日下午10:30:38
*/
public class EagerSingleton {
/**
* 内部直接提供一个eagerSingletonInstance;
* 咱们知道,通常状况下,若是一个变量被static final修饰了,那么该变量将会被视为常量。
* 知足要点:自行建立
*/
private static final EagerSingleton eagerSingletonInstance = new EagerSingleton();
/**
* 提供一个私有的构造函数,这样其余类就没法经过new
* EagerSingleton()来获取对象了,一样也保证了当前类不能够被继承
* 知足要点:某个类只能有一个实例
*/
private EagerSingleton(){}
/**
* 对外提供一个获取实例的方法
* 知足要点:向整个系统提供这个实例
*/
public static EagerSingleton getInstance(){
return eagerSingletonInstance;
}
}
复制代码
方式二:懒汉式post
package com.glmapper.design.singleton;
/**
* 单例模式-懒汉式
* @author glmapper
* @date 2017年12月17日下午10:45:54
*/
public class LazySingleton {
//提供一个私有静态变量,注意区别与饿汉式中的static final。
private static LazySingleton lazySingletonInstance = null ;
//一样须要提供一个私有的构造方法,其做用与饿汉式中的做用同样
private LazySingleton(){}
/**
* 1.使用synchronized来保证线程同步
* 2.实例的具体建立被延迟到第一次调用getInstance方法时来进行
* 3.若是当前实例已经存在,再也不重复建立
*/
public synchronized static LazySingleton getInstance(){
if (lazySingletonInstance == null) {
lazySingletonInstance = new LazySingleton();
}
return lazySingletonInstance;
}
}
复制代码
饿汉式单例类在本身被加载时就本身实例化了,即使加载器是静态的,在饿汉式单例类被加载时仍会将本身实例化。从资源利用角度来讲,这个比懒汉式单例类稍微的差一些。若是从速度和响应时间来看,饿汉式就会比懒汉式好一些。懒汉式在单例类进行实例化时,必须处理好在多个线程同时首次引用此类时的访问限制问题。
方式三:登记式
package com.glmapper.design.singleton;
import java.util.HashMap;
/**
* 单例模式-登记式
* @author glmapper
* @date 2017年12月17日下午10:58:36
*/
public class RegisterSingleton {
//提供一个私有的HashMap类型的registerSingletonInstance存储该RegisterSingleton类型的单例
private static HashMap<String,Object> registerSingletonInstance = new HashMap<>();
//经过static静态代码块来进行初始化RegisterSingleton当前类的实例,并将当前实例存入registerSingletonInstance
static {
RegisterSingleton singleton = new RegisterSingleton();
registerSingletonInstance.put(singleton.getClass().getName(), singleton);
}
/**
* 注意区别,此处提供的是非private类型的,说明当前类能够被继承
*/
protected RegisterSingleton(){}
/**
* 获取实例的方法
*/
public static RegisterSingleton getInstance(String name){
//若是name为空,则那么默认为当前类的全限定名
if (name == null) {
name ="com.glmapper.design.singleton.RegisterSingleton";
}
//若是map中没有查询到指定的单例,则将经过Class.forName(name)来建立一个实例对象,并存入map中
if (registerSingletonInstance.get(name)==null) {
try {
registerSingletonInstance.put(name, Class.forName(name).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
//返回实例
return (RegisterSingleton) registerSingletonInstance.get(name);
}
}
复制代码
登记式单例是Gof为了克服饿汉式和懒汉式单例类均不可被继承的缺点而设计的。
package com.glmapper.design.singleton;
/**
* 登记式-单例-子类
* @author glmapper
* @date 2017年12月17日下午11:14:03
*
*/
public class ChildRegisterSingleton extends RegisterSingleton
{
/**
* 因为子类必须容许父类以构造方法调用产生实例,所以,子类的构造方法必须
* 是public类型的。可是这样一来,就等于说能够容许以new
* ChildRegisterSingleton()的方式产生实例,而没必要在父类的登记中。
*/
public ChildRegisterSingleton(){}
//客户端测试获取实例
public static void main(String[] args) {
ChildRegisterSingleton crs1 = (ChildRegisterSingleton) getInstance(
"com.glmapper.design.singleton.ChildRegisterSingleton");
ChildRegisterSingleton crs2 = (ChildRegisterSingleton) getInstance(
"com.glmapper.design.singleton.ChildRegisterSingleton");
System.out.println(crs1 == crs2);
}
}
返回:true 这个同志们能够自行验证,确定是同样的。可是不能使用new,
由于前提约束是,需在父类中登记的才是单例。
复制代码
方式四:双重检测模式,双重检测方式在某些书上或者文献中说对于java语言来讲是不成立的,可是目前确实是经过某种技巧完成了在java中使用双重检测机制的单例模式的实现,;这种技巧后面来讲;关于为何java语言对于双重检测成例不成立,你们能够在[BLOCH01]文献中看下具体状况。
先来看一个单线程模式下的状况:
package com.glmapper.design.singleton;
/**
* 一个错误的单例例子
* @author glmapper
* @date 2017年12月17日下午11:53:04
*/
public class DoubleCheckSingleton {
private static DoubleCheckSingleton instance=null;
public static DoubleCheckSingleton getDoubleCheckSingleton(){
if (instance == null) {
instance = new DoubleCheckSingleton();
}
return instance;
}
}
复制代码
这个很明显是一个错误的例子,对于A/B两个线程,由于step 1并无使用同步策略,所以线程A/B可能会同时进行// step 2,这样的话,就会可能建立两个对象。那么正确的方式以下:使用synchronized关键字来保证同步。
package com.glmapper.design.singleton;
/**
* 这是一个正确的打开方式哦。。。
* @author glmapper
* @date 2017年12月17日下午11:53:04
*/
public class DoubleCheckSingleton {
private static DoubleCheckSingleton instance=null;
//使用synchronized来保证getDoubleCheckSingleton同一时刻只能被一个线程访问
public synchronized static DoubleCheckSingleton getDoubleCheckSingleton(){
if (instance == null) {
instance = new DoubleCheckSingleton();
}
return instance;
}
}
复制代码
这种方式虽然保证了线程安全性,可是也存在另一种问题:同步化操做仅仅在instance首次初始化操做以前会起到做用,若是instance已经完成了初始化,对于getDoubleCheckSingleton每一次调用来讲都会阻塞其余线程,形成一个没必要要的瓶颈。那咱们就经过使用更加细粒度化的锁,来适当的减少额外的开销。OK,下面再来一个错误的例子:
package com.glmapper.design.singleton;
/**
* 一个错误的单例例子
* @author glmapper
* @date 2017年12月17日下午11:53:04
*/
public class DoubleCheckSingleton {
private static DoubleCheckSingleton instance=null;
//使用synchronized来保证getDoubleCheckSingleton同一时刻只能被一个线程访问
public static DoubleCheckSingleton getDoubleCheckSingleton(){
if (instance == null) { //1
// B线程检测到uniqueInstance不为空
synchronized (DoubleCheckSingleton.class) { //2
if (instance == null) { //3
instance = new DoubleCheckSingleton();//4
// A线程被指令重排了,恰好先赋值了;但还没执行完构造函数。
}
}
}
// 后面B线程执行时将引起:对象还没有初始化错误。
return instance;//5
}
}
复制代码
看起来没什么毛病呀?咱们来分析,两个线程A和B,同时到达1,且都经过了1的检测。此时A到了4,B在2。此时B线程检测到instance不为空,A线程被指令重排了,恰好先赋值了;但还没执行完构造函数;再接下来B线程执行时将引起:对象还没有初始化错误(5)。
对于上面的问题,咱们能够经过volatile关键字来修饰instance对象,来保证instance对象的内存可见性和防止指令重排序。这个也就是前面说到的“技巧”。
private static DoubleCheckSingleton instance=null;
改成:
private static volatile DoubleCheckSingleton instance=null;
复制代码
本篇将单例模式的几种状况进行了分析。后面将会对将java中和Spring中所使用的单例场景进行具体的案例分析。
JAVA中对于单例模式的使用最经典的就是RunTime这个类。
看过上篇文章的小伙伴可能比较清楚,这里RunTime使用的是懒汉式单例的方式来建立的。Runtime提供了一个静态工厂方法getRuntime方法用于获取Runtime实例。Runtime这个类的具体源码分析和只能此处不作分析。
Spring依赖注入Bean实例默认是单例的。Spring中bean的依赖注入都是依赖AbstractBeanFactory的getBean方法来完成的。那咱们就来看看在getBean中都发生了什么。
org.springframework.beans.factory.suppor.AbstractBeanFactory
这个方法体内的代码很是的多,那么咱们本文不是来学习Spring的,因此咱们只看咱们关心的部分,
此处和上面的getBean同样,也是经过模板方法的方式进行调用的。
OK,至此单例模式的学习就结束了,下一篇文章将会介绍工厂模式(简单工厂,工厂方法,抽象工厂)。