一、单例模式的应用场景

  单例模式(singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。J2EE中的ServletContext,ServletContextConfig等;Spring中的ApplicationContext、数据库连接池等。

二、饿汉式单例模式

  饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。它是绝对的线程安全、在线程还没出现以前就实现了,不可能存在访问安全问题。

  优点:没有增加任何锁,执行效率高,用户体验比懒汉式好。

  缺点:类加载的时候就初始化了,用不用都进行,浪费内存。

  Spring 中IoC容器ApplocationContext本身就是典型的饿汉式单例模式:

public class HungrySingleton {
private static final HungrySingleton h = new HungrySingleton(); private HungrySingleton() {
} public static HungrySingleton getInstance() {
return h;
}
}

  饿汉式单例模式适用于单例对象较少的情况。

三、懒汉式单例模式

  被外部调用才会加载:

public class LazySimpleSingleton {
private LazySimpleSingleton() {
} private static LazySimpleSingleton lazy = null; public static LazySimpleSingleton getInstance() {
if (lazy == null) {
lazy = new LazySimpleSingleton();
}
return lazy;
}
}

利用线程创建实例:

public class ExectorThread implements Runnable {
@Override
public void run() {
LazySimpleSingleton simpleSingleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + simpleSingleton);
}
}

客户端代码:

public class LazySimpleSingletonTest {
public static void main(String[] args) {
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("END");
}
}

结果:

END
Thread-1:singleton.Lazy.LazySimpleSingleton@298c37fd
Thread-0:singleton.Lazy.LazySimpleSingleton@6ebc1cfd

可以看到 产生的两个实例的内存地址不同说明产生了两个实例,大家可以通过以下打断点的方式实现不同Thread运行状态见进行切换。

  要解决线程问题第一反应是加 synchronized 加在创建实例的地方:public static synchronized LazySimpleSingleton getInstance(),但当线程数量较多时,用Synchronized加锁,会使大量线程阻塞,就需要更好的解决办法:

    public static LazySimpleSingleton getInstance() {
if (lazy == null) {
synchronized (LazySimpleSingleton.class) {
if (lazy == null) {
lazy = new
LazySimpleSingleton();
}
}

}
return lazy;
}

  synchronized (lock) lock这个对象就是 “锁”,当两个并行的线程a,b,当a先进入同步块,即a先拿到lock对象,这时候a就相当于用一把锁把synchronized里面的代码锁住了,现在只有a才能执行这块代码,而b就只能等待a用完了lock对象锁之后才能进入同步块。但是用到 synchronized 总归是要上锁的,对性能还是有影响,那就用这种方式:用内部类的方式进行懒加载。

public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {
} private static final LazyInnerClassSingleton getIngestance() {
return LazyHolder.LAZY;
} private static class LazyHolder {
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}

  内部类在LazyInnerClassSingleton类加载时加载,解决了饿汉式的性能问题,LazyInnerClassSingleton在内部类加载时,getIngestance()方法被调用之前实例化,解决了线程不安全问题。

四、反射破坏单例

public class LazyInnerClassSingletonTest {
public static void main(String[] args) {
try {
Class<?> clazz = LazyInnerClassSingleton.class;
//通过反射回去私有构造方法
Constructor constructor = clazz.getDeclaredConstructor(null);
//强制访问
constructor.setAccessible(true);
//暴力初始化
Object o1 = constructor.newInstance();
//创建两个实例
Object o2 = constructor.newInstance();
System.out.println("o1:" + o1);
System.out.println("o2:" + o2);
} catch (Exception e) {
e.printStackTrace();
}
}
}

结果:

o1:singleton.Lazy.LazyInnerClassSingleton@1b6d3586
o2:singleton.Lazy.LazyInnerClassSingleton@4554617c

创建了两个实例,违反了单例,现在在构造方法中做一些限制,使得多次重复创建时,抛出异常:

 private LazyInnerClassSingleton() {
if (LazyHolder.class != null) {
throw new RuntimeException("不允许创建多个实例");
}
}

这应该就是最好的单例了,哈哈哈。

五、注册式单例模式

  注册式单例模式又称为登记式单例模式,就是将每个实例都登记到某个地方,使用唯一标识获取实例。注册式单例模式有两种:枚举式单例模式、容器式单例模式。注册式单例模式主要解决通过反序列化破坏单例模式的情况。

1.枚举式单例模式 

public enum EnumSingleton {
INSTANCE;
private Object data; public Object getData() {
return data;
} public void steData(Object data) {
this.data = data;
} public static EnumSingleton getInstance() {
return INSTANCE;
}
}

测试代码:

public class EnumSingletonTest {
public static void main(String[] args) {
try {
EnumSingleton instance1 = EnumSingleton.getInstance();
EnumSingleton instance2 = null;
instance1.steData(new Object()); FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.flush();
oos.close(); FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
instance2 = (EnumSingleton) ois.readObject();
ois.close(); System.out.println(instance1.getData());
System.out.println(instance2.getData());
} catch (Exception e) {
e.printStackTrace();
}
}
}

结果:

java.lang.Object@568db2f2
java.lang.Object@568db2f2

  那枚举式单例是如何解决反序列化得问题呢?

  通过反编译,可以在EnumSingleton.jad文件中发现static{} 代码块,枚举式单例模式在静态代码块中给INSTANCE进行了赋值,是饿汉式单例模式的实现。查看JDK源码可知,枚举类型其实通过类名和类对象找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次。

  当你试图用反射破坏单例时,会报 Cannot reflectively create enum objects ,即不能用反射来创建枚举类型。进入Customer的newInstance(),其中有判断:如果修饰符是Modifier.ENUM,则直接抛出异常。JDK枚举的语法特殊性及反射也为美剧保驾护航,让枚举式单例模式成为一种比较优雅的实现。

2.容器式单例

public class ContainerSingleton {
private ContainerSingleton() {
} private static Map<String, Object> ioc = new ConcurrentHashMap<>(); public static Object getBean(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object o = null;
try {
o = Class.forName(className).newInstance();
ioc.put(className, o);
} catch (Exception e) {
e.printStackTrace();
}
return o;
} else {
return ioc.get(className);
}
}
}
}

spring中使用的就是容器式单例模式。

  

Spring中常见的设计模式——单例模式的更多相关文章

  1. Spring中常见的设计模式——代理模式

    一.代理模式的应用场景 生活中的中介,黄牛,等一系列帮助甲方做事的行为,都是代理模式的体现.代理模式(Proxy Pattern)是指为题对象提供一种代理,以控制对这个对象的访问.代理对象在客户端和目 ...

  2. Spring中常见的设计模式——适配器模式

    一.适配器模式的应用场景 适配器模式(Adapter Pattern)是指将一个类的接口转换成用户期待的另一个接口,使原本接口不兼容的类可以一起工作,属于构造设计模式. 适配器适用于以下几种业务场景: ...

  3. Spring中常见的设计模式——委派模式

    一.委派模式的定义及应用场景 委派模式(Delegate Pattern)的基本作用是负责任务的调用和分配,跟代理模式很像,可以看做特殊情况下的静态的全权代理,但是代理模式注重过程,而委派模式注重结果 ...

  4. iOS中常见的设计模式——单例模式\委托模式\观察者模式\MVC模式

    一.单例模式 1. 什么是单例模式? 在iOS应用的生命周期中,某个类只有一个实例. 2. 单例模式解决了什么问题? 想象一下,如果我们要读取文件配置信息,那么每次要读取,我们就要创建一个文件实例,然 ...

  5. Spring中常见的设计模式——策略模式

    策略模式(Strategy Pattern) 一.策略模式的应用场景 策略模式的应用场景如下: 系统中有很多类,而他们的区别仅仅在于行为不同. 一个系统需要动态的在集中算法中选择一种 二.用策略模式实 ...

  6. Spring中常见的设计模式——工厂模式

    一.简单工厂模式 简单工厂模式(Simple Factory Pattern)由一个工厂对象决定创建哪一种产品类的实例,简单工厂模式适用于工厂类负责创建对象较少的情况,且客户端只需要传入工厂类的参数, ...

  7. Spring中常见的设计模式——原型模式

    1.原型模式应用场景 当遇到大量耗费劳动力的 get,set赋值场景时,如下: public class SetGetParam { public void setParam(UserDto user ...

  8. Spring中常见的设计模式——模板模式

    一.模板模式的应用场景 模板模式又叫模板方法模式(Template Method Pattern),指定义一个算法的骨架,并允许自雷为一个或者多个步骤提供实现.模板模式使得子类可以在不改变算法结果的情 ...

  9. 设计模式:JDK和Spring中常见的设计模式

    设计模式 总结 类 工厂模式 封装创建过程,只对结果负责 BeanFactory.Calender 单例模式 全局唯一 ApplicationContext.Calender 原型模式 多重影分身之术 ...

随机推荐

  1. Gitlab在Centos7上的安装

    一 官网说明 安装步骤:https://about.gitlab.com/install/#centos-7 安装说明:本文只是用来给微服务当配置中心,只是较浅的记录一下安装步骤,后面会详细讲解及在d ...

  2. Android H5混合开发(3):原生Android项目里嵌入Cordova

    前言 如果安卓项目已经存在了,那么如何使用Cordova做混合开发? 方案1(适用于插件会持续增加或变化的项目): 新建Cordova项目并添加Android平台,把我们的安卓项目导入Android平 ...

  3. FPGA时序约束理解记录

    最近整理了一下时序约束的内容,顺便发出来分享记录一下. 任何硬件想要工作正常,均需满足建立和保持时间,至于这个概念不再陈述. 下面将重点介绍两个概念:建立余量和保持余量.FPGA内部进行时序分析无非就 ...

  4. Oracle 相关操作SQL

    SELECT l.session_id sid, s.serial#, l.locked_mode, l.oracle_username, s.user#,l.os_user_name,s.machi ...

  5. 一张图牢记vim常用命令

    1.示例(前提要切到命令状态) ^  光标移动到行首 $  光标移动到行尾 set nu 显示行号 :%s/foo/bar/g 会在全局范围(%)查找foo并替换为bar,所有出现都会被替换(g) 参 ...

  6. Java学习总结之基本数据类型

    Java的数据类型分为基本数据类型和引用数据类型, 今天总结的是基本数据类型.在Java中,一共有八种基本数据类型,分别为整数类型(byte,short,int,long).浮点类型(float,do ...

  7. PHP file_get_contents 读取js脚本的问题

    PHP file_get_contents 读取js脚本的问题 如果文件中带有js脚本 会触发 比方说alert 这个时候 你不用去管他

  8. 过滤广告(只能发布 [a-zA-z0-9及汉字,;?.]) ,排除其他特殊符号

      /** * 过滤广告(只能发布 [a-zA-z0-9及汉字,;?.]) ,排除其他特殊符号 * Created by 1 on 2015/8/19. */ public class FilterA ...

  9. 第五天、vim,重定向,用户和组管理

    第五天.vim,重定向,用户和组管理 vim vi:Visual editor,文本编辑器 行编辑器:sed 全屏编辑器:vim,vi,nano 其他编辑器gedit,gvim 定义别名让vi等于vi ...

  10. css字体图标的制作

    我介绍的这个办法是直接在 "阿里巴巴图标库"中制作的,方便快捷 1. 首先到 "阿里巴巴图标库"中找到你想要的图片,然后选择加入购物车 接着我们点击右上角的购物 ...