1. 概述
它保证一个类只有一个实例,并提供一个全局访问点来获取该实例。这种模式通常用于创建那些在整个系统中只需要一个实例的对象,比如配置管理器、线程池、日志对象等。
2. 结构
单例模式的主要角色:
单例类:包含单例实例的类,通常将构造函数声明为私有。
静态成员变量:用于存储单例实例的静态成员变量。
获取实例方法:静态方法,用于获取单例实例。
私有构造函数:防止外部直接实例化单例类。
线程安全处理:确保在多线程环境下单例实例的创建是安全的。
3. 实现
3.1饿汉式(预加载)
概述:
类加载就会导致该单实例对象被创建。
URL图:
实现方式一:
方式一(定义静态变量实例):
**
* 饿汉式:预加载
* 静态成员变量
*/
public class SingleObject {
//定义静态成员变量获取本类的实例
private static final SingleObject INSTANCE = new SingleObject ();
//私有构造方法,避免通过new关键字来实例化对象,保证只存在一个实例
private SingleObject (){}
//提供一个公共的访问类,让外界获取该对象
public static SingleObject getINSTANCE() {
return INSTANCE;
}
}
方式二(静态代码块初始化实例):
/**
* 饿汉式的第二种实现方式:
* 使用静态代码块的方式
*/
public class SingleObject {
//声明Singleton2类型的静态常量成员变量,但是没有初始化,在静态代码块中对其进行初始化
private static final SingleObject INSTANCE;
//在静态代码块中对成员常量初始化
static {
INSTANCE = new SingleObject ();
}
//私有化构造方法,避免外部使用new关键字创建实例
private SingleObject (){}
//定义公共方法,对外提供获取单例实例的接口
public static SingleObject getINSTANCE() {
return INSTANCE;
}
}
分析:
没有使用该单例对象,该对象就被加载到了内存,会造成内存的浪费。
实现方式二(枚举方式):
概述:
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
/**
* 枚举方式实现单例
* 枚举类型实现单例模式是极力推荐的实现模式。因为枚举是线程安全的,并且只会装载一次,设计者充分
* 利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一不会被
* 破坏的单例实现模式。
*
* 枚举类型属于饿汉式方式,即预加载模式,在不考虑内存空间的情况下,使用枚举方式是比较好的一种方式
*/
public enum SingleObject {
INSTANCE; //属于实例名 完整: private static final INSTANCE = new SingleObject (); 类似于第一个静态变量初始化实现单例模式。
}
3.2 懒汉式(懒加载)
概述:
类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建,从而避免了内存的浪费。
实现方式一(线程不安全):
/**
* 懒汉式 懒加载 lazy loading
*/
public class SingleObject {
//声明Singleton类型的变量
private static SingleObject instance; //只是声明,并没有赋值
//私有化构造方法
private SingleObject (){}
/**
* 第一种方式:
* 对外提供获取单例的接口方法,下面这种方法存在线程安全问题。
*/
public static SingleObject getInstance(){
if(instance == null){
instance = new SingleObject ();
}
return instance;
}
}
分析:
即使类第一次使用时候,也并没有进行对象的赋值操作,直到调用getInstance方法判断为null时候才会创建,实现了使用才创建的行为。避免了内存的浪费。但是,多线程下,会出现线程安全问题,会导致同时创建多个对象,违背一个类只有一个实例原则。
实现方式二(线程安全):
1. synchronized关键字
使用synchronized同步关键字来解决线程安全问题
/**
* 懒汉式 懒加载 lazy loading
*/
public class SingleObject {
//声明Singleton类型的变量
private static SingleObject instance; //只是声明,并没有赋值
//私有化构造方法
private SingleObject (){}
/**
* 第二种方式:
* 使用synchronized同步关键字来解决线程安全问题
* synchronized加载getInstace()函数上确实保证了线程的安全。
* 但是,如果要经常的调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程。
* @return
*/
public static synchronized SingleObject getInstance() {
if(instance==null){
instance = new SingleObject ();
}
return instance;
}
}
分析:
加锁实现了线程安全问题。但是同时导致该方法执行效率变低。从代码可以看出,当调用过一次getInstance()方法后,线程安全问题就不会在存在,此时若每次重新执行getInstance()方法都要进行重锁检查,导致效率特别低。
2. 双重检查
/**
* 懒汉式的双重检查锁方式
*
*/
public class SingleObject implements Serializable {
//使用volatile 关键字可以保证可见性和有序性
private static volatile SingleObject instance;
private SingleObject (){}
public static SingleObject getInstance() {
/**
* 使用双重检查解决了单例、性能、线程安全问题,但是在多线程环境中由于JVM在实例化对象的时候会进行
* 优化和指令的重排序操作,可能会导致空指针异常问题,这时候需要使用volatile关键字,可以保证可见性
* 和有序性
*/
//第一次判断,如果instance的值不为null,不需要抢占锁,再返回对象
if(instance==null){
synchronized (SingleObject .class){
//第二次判断
if(instance==null){
instance = new SingleObject ();
}
}
}
return instance;
}
}
分析:
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题。
实现方式三(静态内部类):
/**
* 懒汉式方式4,静态内部类方式
* 静态内部类单例模式中实例由内部类创建,由于JVM在加载外部内的过程中,是不会加载静态内部类的,只有内部类 的属性或者方法被调用时,才会被加载,并初始化其静态属性。静态属性由于被static修饰,所以只会被实例化一次, 并且严格保证实例化顺序
*/
public class SingleObject {
private static boolean flag = false;
//私有化构造器
private SingleObject (){
}
//定义静态内部类,私有化属性
private static class SingletonHolder{
private static final SingleObject INSTANCE = new SingleObject ();
}
//对外提供静态方法获取该对象
public static SingleObject getInstance() {
return SingletonHolder.INSTANCE;
}
}
4. 存在问题
序列化和反序列化会破坏单例模式,通过实现 Serializable接口可以进行解决.
反射同样也可以破坏到单例模式,可以通过在单例类中对构造器进行限制,当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。