介绍
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。作用是控制实例数目,节省系统资源;
特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
单例模式分为两种:
- 饿汉式:类加载时就创建好单例对象,不管之后会不会被用到;
- 懒汉式:需要用到单例对象调用getInstance()方法时,才会真正创建好该对象;
无论是饿汉式还是懒汉式,有很多实现单例类的写法,这些不同的实现方式也各有利弊。
通过JDK的序列化和反序列化的方式多次创建单例对象,或通过反射调用私有构造方法来创建多个单例对象会破坏单例模式;
对单例模式的实现方式、存在的问题以及解决方案总结如下:
单例模式的实现
实现单例模式基本概括为下面三个步骤:
- 构造器私有化
- 声明单例对象为私有静态的成员变量(饿汉式直接初始化创建对象,懒汉式默认为空)
- 提供一个获取单例对象的公共访问入口:getInstance()
下面为具体代码的实现:
饿汉式(EagerSingleton)
/**
* 单例模式:饿汉式
* 方式1:声明静态变量并初始化方式
*/
class EagerSingleton1 {
// 1.构造器私有化
private EagerSingleton1(){}
// 2.创建对象为静态成员变量
private static EagerSingleton1 instance = new EagerSingleton1();
// 3.提供获取对象的公共访问入口
public static EagerSingleton1 getInstance(){
return instance;
}
}
/**
* 单例模式:饿汉式
* 方式2:静态代码块初始化的方式
*/
class EagerSingleton2 {
// 1.私有化构造方法
private EagerSingleton2(){}
// 2.声明单例对象为静态成员变量
private static EagerSingleton2 instance;
// 3.静态代码块中初始化对象
static {
instance = new EagerSingleton2();
}
public static EagerSingleton2 getInstance() {
return instance;
}
}
/**
* 单例模式:饿汉式
* 方式3:枚举方式
* 特点:唯一一种不会被反射或序列化破坏的单例实现模式
*/
enum EagerSingleton3 {
INSTANCE;
}
懒汉式(LazySingleton)
/**
* 单例模式:懒汉式
* 方式1:线程不安全
*/
class LazySingleton1 {
// 1.私有构造方法
private LazySingleton1(){}
// 2.声明单例对象为私有的静态成员变量
private static LazySingleton1 instance = null;
// 3.提供公共获取对象的方法
public static LazySingleton1 getInstance(){
if (instance == null){
instance = new LazySingleton1();
}
return instance;
}
}
/**
* 单例模式:懒汉式
* 方式2:方法上加synchronized关键字
* 问题:线程安全,但效率低下
*/
class LazySingleton2{
// 1.构造方法私有化
private LazySingleton2(){}
// 2.声明单例对象为私有静态成员变量
private static LazySingleton2 instance = null;
// 3.提供公共访问获取对象的方法(同步加锁保证线程安全)
private static synchronized LazySingleton2 getInstance(){
if(instance == null){
instance = new LazySingleton2();
}
return instance;
}
}
/**
* 单例模式:懒汉式
* 方式3:双重检查锁,线程安全,效率高
* 特点:需要加volatile关键字,否则在多线程的情况下,可能会出现空指针问题。
* 出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
*
*/
class LazySingleton3 {
// 1.构造方法私有化
private LazySingleton3(){}
// 2.声明单例对象为私有静态成员变量
// 加volatile关键字:在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
// 使用volatile关键字可以保证可见性和有序性。
private static volatile LazySingleton3 instance = null;
// 3.提供公共访问获取对象的方法(同步加锁保证线程安全)
public static LazySingleton3 getInstance(){
// 不为空直接返回对象
if (instance == null){
// 为空才加锁
synchronized (LazySingleton3.class){
if (instance == null){
instance = new LazySingleton3();
}
}
}
return instance;
}
}
/**
* 单例模式:懒汉式
* 方式4:静态内部类方式
* 特点:开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
*/
class LazySingleton4 {
// 1.私有构造方法
private LazySingleton4(){}
// 2.声明静态内部类
private static class SingletonHolder {
// 3.在静态内部类中声明外部类单例对象为静态私有的成员变量
private static final LazySingleton4 INSTANCE = new LazySingleton4();
}
// 3.提供公共访问获取对象的方法
public static LazySingleton4 getInstance(){
return SingletonHolder.INSTANCE;
}
}
JDK序列化破坏单例模式的问题
问题演示
package com.glong.singleton;
import java.io.*;
public class SerializingProblem {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 得到单例对象
Singleton instance = Singleton.getInstance();
// 序列化对象
writeObj(instance);
// 反序列化两次对象
Singleton singleton1 = (Singleton) readObject();
Singleton singleton2 = (Singleton) readObject();
// 判断是否相等
System.out.println(singleton1 == singleton2); // false
}
public static Object readObject() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));
Object obj = ois.readObject();
ois.close();
return obj;
}
/**
* 序列化对象
*
* @param <T> 要被序列化的对象
* @throws Exception IOException
*/
public static <T> void writeObj(T t) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));
oos.writeObject(t);
oos.close();
}
}
class Singleton implements Serializable{
private Singleton(){}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
解决
在Sigleton类中添加readResolve()方法:
class Singleton implements Serializable{
private Singleton(){}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
// 解决序列化反序列化多次获取多个单例对象问题
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
再次比较两次获取的对象是否相等的结果会变为true
反射破坏单例模式的问题
问题演示
package com.glong.singleton;
import java.lang.reflect.Constructor;
public class ReflectionProblem {
public static void main(String[] args) throws Exception {
// 获取class对象
Class<SingletonReflection> singleton = SingletonReflection.class;
// 获取私有的构造方法
Constructor<SingletonReflection> constructor = singleton.getDeclaredConstructor();
// 取消访问检查
constructor.setAccessible(true);
// 创建对象
SingletonReflection instance1 = constructor.newInstance();
SingletonReflection instance2 = constructor.newInstance();
// 判断两个对象是否相等
System.out.println(instance1 == instance2); // false
}
}
class SingletonReflection {
private SingletonReflection(){}
private static class SingletonHolder {
private static final SingletonReflection INSTANCE = new SingletonReflection();
}
public static SingletonReflection getInstance(){
return SingletonHolder.INSTANCE;
}
}
解决
在私有构造方法判断是否创建过对象这个对象,若创建过直接抛出异常。
class SingletonReflection {
private static volatile SingletonReflection instance;
// 标记单例对象是否被创建过
private static boolean flag = false;
private SingletonReflection(){
// 解决通过反射创建多个单例对象的问题
synchronized (SingletonReflection.class){
if(flag){
// 尝试创建多个直接抛出异常
throw new RuntimeException("单例对象只能创建一个!");
}
flag = true;
}
}
public static SingletonReflection getInstance(){
if (instance == null){
synchronized (SingletonReflection.class){
if (instance == null){
instance = new SingletonReflection();
}
}
}
return instance;
}
}
此时再次执行代码会抛出异常。