(一) 单例模式概述
单例模式(Singleton Pattern): 是Java中最简单的设计模式之一, 它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类, 该类负责创建自身的实例对象 且 创建的对象只存在一个实例. 这个类提供了一种访问其唯一对象的方式, 可以直接访问.
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例 且 无法通过new创建实例
- 单例类必须给所有其他对象提供这一实例
单例模式的种类:
- 饿汉式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式
- 懒汉式(线程不安全)
- 懒汉式(线程安全, 同步方法)
- 懒汉式(线程安全, 同步代码块)
- 双重检查
- 静态内部类
- 枚举
(二) 单例模式实现的方式
1. 饿汉式
步骤:
- 静态常量实例
- 构造器私有化, 防止new创建实例
- 类内部创建对象
- 向外暴露一个静态的获取实例的公共方法
/**
* 单例模式_饿汉式
*/
public class SingletonPattern_hungryType {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
}
}
class Singleton {
// 静态常量实例
// public final static Singleton instance= new Singleton();
// 静态代码块 创建实例
public final static Singleton instance;
static {
instance = new Singleton();
}
/**
* 构造方法私有化
*/
private Singleton() {}
/**
* 向外暴露一个静态的获取实例的公共方法
*
* @return
*/
public static Singleton getInstance() {
return instance;
}
}
饿汉式的单例模式 优缺点:
- 优点: 对象在类装载(类进入堆中)的时候就完成实例化, 避免了线程同步的问题
- 缺点: 在类装载的时候就完成实例化, 没有达到Lazy Loading(懒加载)的效果. 如果从开始至终都没有使用该实例, 则会造成内存浪费 (导致类装载的原因有很多种, 如: 这个类中还有其他静态方法)
2. 懒汉式
懒汉式: 在该模式只有你需要对象时(调用getInstance方法), 才会实例化单例对象.
/**
* 单例模式_懒汉式
*/
public class SingletonPattern_slackerType {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
}
}
class Singleton {
// 静态成员属性
public static Singleton instance;
/**
* 构造方法私有化
*/
private Singleton() {
}
/**
* 调用getInstance方法, 才实例化单例对象
* 通过同步方法(synchronized)保证多线程下获取的对象是同一个实例
*
* @return
*/
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式的单例模式 优缺点:
- 优点: 实现Lazy Loading(懒加载)的效果, 只有在真正使用时才会获取该对象的实例
- 缺点: 为了保证多线程下获取的对象是同一个实例, 使用了同步方法(synchronized), 其他线程就必须等待整个方法执行完毕, 引起线程阻塞, 效率低下.
3. 双重检查
双重检查: 这种方式采用双锁机制, 两次执行 if (instance == null) 保证安全且在多线程情况下能保持高性能。
/**
* 单例模式_双重检查
*/
public class SingletonPattern_doubleCheck {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
/**
* volatile 修饰 静态成员变量
* 被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象(内存可见性)
* Java内存模型: 各个线程会将共享变量(instance)从主内存中拷贝到工作内存,然后执行引擎会基于工作内存中的数据进行操作处理
* volatile修饰的变量给java虚拟机特殊的约定,线程对volatile变量的修改 会同时修改 工作内存 和 主内存
* 读取volatile修饰的变量, 会优先读取 主内存的变量, 如与工作内存值不一样会同步再返回
*/
public static volatile Singleton instance;
/**
* 构造方法私有化
*/
private Singleton() {
}
/**
* 调用getInstance方法, 才实例化单例对象(懒加载)
* 通过两次 if (instance == null) + 同步代码块, 解决线程安全问题且提高了效率
*
* @return
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
instance = new Singleton();
}
return instance;
}
}
双重检查的单例模式 优缺点:
- 双重检查(Double-Check) 概念是多线程开发中经常使用到的, 在代码中, 我们进行了两次 if (instance == null) 检查, 保证线程安全
- 实例代码只用执行了一次, 后面在次访问时, 判断 if (instance == null), 直接return实例化对象, 也避免反复执行方法同步
- 线程安全, 延迟加载, 效率高
4. 静态内部类
采用静态内部类来创建对象实例, 静态内部类有已下特点:
- 当一个类在类装载(初始化类, 会默认执行空的构造方法、静态代码块)时, 其 静态内部类 不会被装载
- 只有外部类使用到 静态内部类 时, 静态内部类 才会被装载
/**
* 单例模式_静态内部类
*/
public class SingletonPattern_staticInnerClass {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
/**
* 构造方法私有化
*/
private Singleton() {}
/**
* 通过静态内部类获取对象实例
*
* @return
*/
public static Singleton getInstance() {
return SingletonInstance.SINGLETON;
}
/**
* 静态内部类SingletonInstance: 类中存在 Singleton的静态常量 且 实例化对象
*/
private static class SingletonInstance {
private static final Singleton SINGLETON = new Singleton();
static {
System.out.println("静态内部类装载(初始化)");
}
}
}
双重检查的单例模式 优缺点:
-
这种方式采用了类装载的机制来保证只初始化一个实例
-
静态内部类在 Singleton类装载时并不会立即实例化, 而是在需要实例化时, 调用getInstance方法, 才会装载SingletonInstance类, 从而完成Singleton的实例化
-
类的静态属性只会在第一次加载类的时候初始化, 因此是 JVM 保证了线程的安全性, 在类进行初始化时, 别的线程是无法进入的
-
优点: 避免了线程不安全, 利用静态内部类特点实现延迟加载, 效率高
5. 枚举
/**
* 单例模式_枚举
*/
public class SingletonPattern_enum {
public static void main(String[] args) {
SingletonEnum instance1 = SingletonEnum.INSTANCE; // 调用SingletonEnum枚举类的无参构造方法
SingletonEnum instance2 = SingletonEnum.INSTANCE;
System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
/**
* SingletonEnum枚举: 只有一个 INSTANCE 的实例
*/
enum SingletonEnum {
INSTANCE;
}
借助JDK1.5中添加的枚举来实现单例模式, 不仅能避免多线程同步问题, 而且还能防止反序列化重新创建对象(最简单实现的单例模式)
(三) 总结
单例模式, 就是采取一定的方法保证在整个的软件系统中, 对某个类只能存在一个对象实例, 并且该类只提供一个取得对象实例的静态方法.
- 单例模式保证了系统内存中该类只存在一个实例对象, 节省了系统资源, 对于一些需要频繁创建销毁的对象, 使用单例模式可以提高系统性能, 如: 工具类对象、频繁访问数据库或文件的对象(数据源、session工场等)
- 当想实例化一个单例对象的时候, 必须要记住使用相应的获取对象的静态方法, 而不是使用new的方式创建