设计模式-单例模式

@TOC
如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举方式来实现单例。
分类:https://blog.csdn.net/u011595939/article/details/79972371
实例分析线程安全等问题:https://blog.csdn.net/chenchaofuck1/article/details/51702129

二、饿汉模式

1.饿汉模式实现(单例对象立即加载)

  1. • 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
  2. • 问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class SingletonDemo02 {
    //创建对象
    private static /*final*/ SingletonDemo02 s = new SingletonDemo02();
    private SingletonDemo02() {
    }//私有化构造器
    //获得唯一可用的对象
    public static /*synchronized*/ SingletonDemo02 getInstance() {
    return s;
    }
    }
  3. 测试
    1
    2
    3
    4
    5
    6
    7
    public class Client {
    public static void main(String[] args) {
    SingletonDemo02 s = SingletonDemo02.getInstance();
    SingletonDemo02 s2 = SingletonDemo02.getInstance();
    System.out.println(s==s2); //结果为true
    }
    }

    三、懒汉模式

    1.懒汉模式实现(单例对象延迟加载)

  4. 要点:
    – lazy load! 延迟加载, 懒加载! 真正用的时候才加载!
  5. 问题:
    – 资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class SingletonLazy {
    /** 懒汉模式-多线程环境,但效率不高 */
    //getInstanceB()方法加上同步关键字sychronized保证在多线程环境下我们还是只能得到该类的一个实例
    // 但每次调用getInstanceB()方法时都被synchronized关键字锁住了,会引起线程阻塞,影响程序的性能。
    private static SingletonLazy instance;

    public static synchronized SingletonLazy getInstance() {
    if (instance == null) {
    instance = new SingletonLazy();
    }
    return instance;
    }
    }

四、双重检测实现

  • 这个模式将同步内容下方到if内部,提高了执行的效率
    不必每次获取对象时都进行同步,只有第一次才同步
    创建了以后就没必要了。
  • 问题: 由于编译器优化原因和JVM底层内部模型原因,
    偶尔会出问题。不建议使用。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SingletonLazy {
/**懒汉模式-双重检验锁-线程安全且并行效率高*/
//多线程不影响程序的性能,不让线程每次调用getInstanceC()方法时都加锁,
// 而只是在实例未被创建时再加锁,在加锁处理里面还需要判断一次实例是否已存在。
private static SingletonLazy instance;

public static SingletonLazy getInstance() {
// 先判断实例是否存在,若不存在再对类对象进行加锁处理
if (instance == null) { //Single Checked
synchronized (SingletonLazy.class) {
if (instance == null) { //Double Checked
instance = new SingletonLazy();
}
}
}
return instance;
}
}

五、静态内部类

  • 静态内部类实现方式(也是一种懒加载方式)-懒汉模式
  • 要点:
    – 外部类没有static属性,则不会像饿汉式那样立即加载对象。
    – 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final
    类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
    – 兼备了并发高效调用和延迟加载的优势!
  • 实现*
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Singleton {
    private static class SingletonClassInstance {
    private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
    return SingletonClassInstance.instance;
    }

    private Singleton() {
    }
    }

六、枚举方式

饿汉模式

  • 问题:
    – 反射可以破解上面几种(不包含枚举式)实现方式!(可以在构造方法中手动抛出异常控制)
    – 反序列化可以破解上面几种((不包含枚举式))实现方式!
    • 可以通过定义readResolve()防止获得不同对象。
    – 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
  • 优点:
    – 实现简单
    – 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
  • 缺点:
    – 无延迟加载
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public enum Singleton {
    /**
    * 定义一个枚举的元素,它就代表了Singleton的一个实例。
    */
    INSTANCE;
    /**
    * 单例可以有自己的操作
    */
    public void singletonOperation() {
    //功能处理
    }
    }

一、单例模式

1.核心作用

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

2.应用场景

–Windows的Task Manager(任务管理器)就是很典型的单例模式
– 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
– 网站的计数器,一般也是采用单例模式实现,否则难以同步。
– 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
– 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
– Application 也是单例的典型应用(Servlet编程中会涉及到)
– 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理

3.单例模式的优点

  1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
  2. 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理

    4.常见的五种单例模式实现方式:

  3. 主要:
    • 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
    • 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
  4. 其他:
    • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
    • 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
    • 枚举单例(线程安全,调用效率高,不能延时加载)