一、 概述
1、 定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例设计模式解决的问题:就是可以保证一个类在内存中的对象唯一性。 比如多个程序使用同一个配置信息对象时,就需要保证该对象的唯一性。
2、 特点
- 单例类确保自己只有一个实例;
- 单例类必须自己创建自己的实例;
- 单例类必须为其他对象提供唯一的实例(即对外提供一个方法让其他程序可以获取该对象)。
3、 优点
- 防止在应用程序中实例化多个对象。这就节约了开销,每个实例都要占用一定的内存,创建对象时需要时间和空间;
- 作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信;
- 使用单例模式最核心的一点是体现了面向对象封装特性中的“单一职责”原则。
二、 实现
- 饿汉方式:指全局的单例实例在类装载时构建
- 懒汉方式:指全局的单例实例在第一次被使用时构建。
1、 饿汉方式(线程安全)
“饿汉方式” 就是说JVM
在加载这个类时就马上创建此唯一的单例实例,不管你用不用,先创建了再说,如果一直没有被使用,便浪费了空间,典型的空间换时间,每次调用的时候,就不需要再判断,节省了运行时间
class Singleton{
//类已加载,对象就已经存在了
private static Singleton singleInstance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleInstance ;
}
}
class SingleDemo{
public static void main(String[] args){
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton. getInstance();
System.out.println(s1 == s2);//true
}
}
2、 懒汉方式
懒汉式” 就是说单例实例在第一次被使用时构建,而不是在JVM
在加载这个类时就马上创建此唯一的单例实例。
class Singleton{
//类加载进来,没有对象,只有调用了getInstance方法时,才会创建对象
//延迟加载形式
private static Singleton singleInstance = null;
private Singleton(){}
public static Singleton getInstance(){
if(singleInstance == null)
singleInstance = new Singleton ();
return singleInstance ;
}
}
class SingleDemo{
public static void main(String[] args){
Singleton s1 = Singleton. getInstance();
Singleton s2 = Singleton. getInstance();
System.out.println(s1 == s2);//true
}
}
三、 多线程补充
懒汉方式很明显是线程不安全的,如果多个线程同时访问getInstance()
方法时就会出现问题,使用两种方式进行解决。
1、 加锁
第一种常见的方式就是在getInstance()
方法前加上synchronized
关键字,但是每当一个线程访问getInstance()
这个方法时候,都要经过加锁,这其实是没必要的。
public static synchronized Singleton getInstance() {
if (instance == null) {
singleInstance = new Singleton();
}
return singleInstance ;
}
2、 双重加锁
利用双重检查加锁,首先检查是否实例已经创建,如果尚未创建,“才”进行同步。这样以来,只有一次同步,这正是我们想要的效果
public class Singleton {
//volatile保证,当singleInstance变量被初始化成Singleton实例时,多个线程可以正确处理singleInstance变量
private volatile static Singleton singleInstance;
private Singleton() {
}
public static Singleton getInstance() {
//检查实例,如果不存在,就进入同步代码块
if (singleInstance== null) {
//只有第一次才彻底执行这里的代码
synchronized(Singleton.class) {
//进入同步代码块后,再检查一次,如果仍是null,才创建实例
if (singleInstance== null) {
singleInstance= new Singleton();
}
}
}
return singleInstance;
}
}
Note:
- 双重为空判断:外面的判断用于提高性能 ,里面的判断用于避免违背单例的单。例如两个线程同时到达
synchronized
一个进入创建一个进行等待,第一个线程创建完毕,第二个不需要重新创建需要判断一下 volatile
修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile
关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式
四、一种更好的实现方式(Java)
饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。这里引入
Initialization Demand Holder (IoDH)
一个线程安全的、无需synchronization
的、且比无竞争的同步高效的单例模式。
//Initialization on Demand Holder
class Singleton {
private Singleton() {
}
private static class HolderClass {
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.INSTANCE;
}
public static void main(String args[]) {
Singleton s1 = Singleton. getInstance();
Singleton s2 = Singleton. getInstance();
System.out.println(s1==s2);//true
}
}
Note:
- 当
Singleton
类被JVM
加载时,由于这个类没有其他静态属性,其初始化过程会顺利完成。直到内部静态类Holder
调用getInstance()
时才会被初始化。 - 当
HolderClass
第一次被执行时,JVM
会加载并初始化该类。由于HolderClass
含有静态方法INSTANCE
,因此会一并初始化INSTANCE
。JLS
保证了类的初始化阶段是连续的。这样,所有后序的并发调用getInstance()
都会返回一个正确初始化的INSTANC
E而不会有额外同步开销。 - 任何初始化失败都会导致单例类不可用。也就是说,
IoDH
这种实现方式只能用于能保证初始化不会失败的情况。(单例的构造函数初始化不能失败)