目录
设计模式简介
将设计者的思维融入大家的学习和工作中,更高层次的思考!
• 创建型模式:
– 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
• 结构型模式:
– 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
• 行为型模式:
– 模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
本次文章介绍的是单例模式的五种实现方式。
单例模式的简介
• 核心作用:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
• 常见应用场景:
– Windows的Task Manager(任务管理器)就是很典型的单例模式
– windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
– 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
– 网站的计数器,一般也是采用单例模式实现,否则难以同步。
– 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
– 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
– 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
– Application 也是单例的典型应用(Servlet编程中会涉及到)
– 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
– 在servlet编程中,每个Servlet也是单例
– 在spring MVC框架/struts1框架中,控制器对象也是单例
• 单例模式的优点:
– 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,
则可 以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
– 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
• 常见的五种单例模式实现方式:
– 主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
– 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举单例(线程安全,调用效率高,不能延时加载)
单例模式的实现
实现方式一:饿汉式(单例对象立即加载)
• 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
• 问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!
需要注意的三点:
第一点:需要将构造器私有,构造器私有之后别人就访问不了了,只有自己可以用;
第二点:提供一个属性,这个属性是static变量,并且是私有的,static变量是类变量,从属于这个类,那么这个类就只有这么一个属性,它就指定了这个对象;
第三点:提供一个开放的方法,别人只能从这里取对象。
以上三点的描述,详细代码见以下的第一个!!
/**
* 测试饿汉式单例模式
* --所谓饿汉式,表示的是很饿,上来就把你吃了,相当于当类加载器加载以下类的时候,
* 加载的时候就把这个对象New出来,初始以下的静态属性,不管你要不要,上来就给建好。
* 这个就是立即加载。这个就是不好的地方。
*
* --特点:
* 1.线程安全:方法前不需要加synchronized,因为我们去创建对象时,在类初始化时立刻加载,
* 在类加载器加载对象时是一个天然的线程安全模式。
* 不存在创建对象/初始化属性多线程不安全的问题,所以天然线程安全。
*
* 2.显然不加同步标记的话,效率就高。
*/
public class SingletonDemo01 {
//类初始化时,立即加载这个对象(没有延时加载的优势),加载类时,天然的是线程安全的。
private static SingletonDemo01 instance = new SingletonDemo01();
private SingletonDemo01(){
}
//方法没有同步,调用效率高
public static SingletonDemo01 getInstance(){
return instance;
}
}
实现方式二:懒汉式(单例对象延迟加载)
• 要点:
– lazy load! 延迟加载, 懒加载! 真正用的时候才加载!
• 问题:
– 资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。
–调用的效率低。
注意:如果类创建对象时用到的代价很高,那么就使用延时加载,也就是懒汉式;如果类调用效率非常频繁,就用饿汉式。
代码为:
/**
* 懒汉式单例模式
*/
public class SingletonDemo02 {
//类初始化时,不初始化这个对象(实现了延时加载,真正用到的时候才去创建)
private static SingletonDemo02 instance;
private SingletonDemo02(){ //私有化构造器
}
//方法同步,调用效率低
public static synchronized SingletonDemo02 getInstance(){
if(instance==null){
instance = new SingletonDemo02();
}
return instance;
}
}
实现方式三:双重检测锁实现(不建议使用)
• 这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,
只有第一次才同步创建了以后就没必要了。
• 问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。
代码如下所示:
/**
* 双重检查锁模式
*/
public class SingletonDemo06 {
private volatile static SingletonDemo06 instance;
private SingletonDemo06(){}
public static SingletonDemo06 getInstance(){
//先判断对象是否已经实例化过,没有实例化才能进入加锁代码
if(instance==null){
//对类对象加锁
synchronized(SingletonDemo06.class){
if(instance == null){
instance = new SingletonDemo06();
}
}
}
return instance;
}
}
实现方式四:静态内部类实现方式(懒加载方式)
基本思路:首先是静态内部类,在里面定义单例对象,然后也提供一个方法getInstance(),通过调用静态内部的方法进行访问,构造器私有,这种方式不仅线程安全,还是懒加载模式。
当我们第一次去初始化这个类的时候,并不会立即初始化它的静态内部类,当真正要用的时候,才会通过getInstance()这个方法去调用SingletonClassInstance.instance;从而去加载内部类中的代码,使用时也不存在同步的问题。调用效率也不错,很多开源的都用这个。
• 要点:
– 外部类没有static属性,则不会像饿汉式那样立即加载对象。
– 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。 instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
– 兼备了并发高效调用和延迟加载的优势!
优点:
1、线程安全,还是懒加载模式
2、调用效率高
3、实现了延时加载
代码实现:
/**
* 静态内部类实现单例模式
*
* 优点:
* 1、线程安全,还是懒加载模式
* 2、调用效率高
* 3、实现了延时加载
*/
public class SingletonDemo03 {
private static class SingletonClassInstance{
private static final SingletonDemo03 instance = new SingletonDemo03();
}
public static SingletonDemo03 getInstance(){
return SingletonClassInstance.instance;
}
private SingletonDemo03(){}
}
实现方式五:枚举
• 优点:
– 实现简单
– 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
• 缺点:
– 无延迟加载
代码如下:
/**
* 使用枚举方式实现单例模式
• 优点:
– 实现简单
– 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
*/
public enum SingletonDemo04 {
/**
* 定义一个枚举元素,它就代表了Singleton的一个实例
*/
INSTANCE;
/**
* 单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
}
单例模式总结
• 常见的五种单例模式实现方式
– 主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
– 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举式(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)
• 如何选用?
– 单例对象 占用 资源 少,不需要 延时加载:
• 枚举式 好于 饿汉式
– 单例对象 占用 资源 大,需要 延时加载:
• 静态内部类式 好于 懒汉式
寄言:
学习设计模式时一定要动手去敲代码,再加以理解,尤其是饿汉式和懒汉式,一定要熟悉到自己能够手写出来,因为面试的时候经常会让你手写出来(亲测)。