个人对单例模式的一点理解

单例模式的几种写法以及个人的一点理解

(如有误解请指出,感激不尽)

单例模式简介

  单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
  在某些场景下,我们可能只需要创建一个实例,并且确保其他所有的对象访问时都是这一个实例,然后利用这个实例来控制整个系统。

适用场景
  1. windows的任务管理器(Task Manager)。
  2. windows的回收站(Recycle Bin)。
  3. 外部打印机的管理:当一台电脑连接多个打印机的时候,也只有一个打印后台处理服务(PrinterSpooler),以避免多个打印机同时打印同一个文件。
  4. 部分软件的一些属性文件的存放和调用,用一个对象来统一管理。

单例模式的实现思路

目标:保证一个类中只有一个实例。
思路:

  1. 外部不可创建单例类的实例对象,单例类的构造方法必须自己定义为私有。
  2. 单例类必须自己创建自己的唯一实例。
  3. 在单例类提供一个静态方法,在调用该方法时返回给其他对象唯一实例。

单例模式常见的写法


  1. 饿汉式:单例实例在加载类的时候就创建。
public class SingletonHungary{
   	private static final SingletonHungary instance = new SingletonHungary(); // 单例类本身创建实例

   private SingletonHungary() {} // 私有化单例类构造器

   public static SingletonHungary getInstance() {
   	return instance; //访问该静态方法时返回单例类实例
   }
}

优点:在类加载的时候就完成了实例创建,避免了多线程同步的问题。线程安全,执行效率高。
缺点:只要加载了该类,实例对象就创建了。资源效率不高,而且可能getInstance()永远不会执行使用,造成资源的浪费。


  1. 懒汉式:在需要时创建单例实例。
public class SingletonLazy {
	private static SingletonLazy instance;

	private SingletonLazy() {}// 私有化单例类构造器

	public static SingletonLazy getInstance() {
		//访问时检查是否有单例实例,无则创建并返回该实例。有则直接返回该单例类实例
		if (instance == null) {
			instance = new SingletonLazy();
		}
		return instance; //访问该静态方法时返回单例类实例
	}
}

优点:资源利用率高,避免了在使用该类其他静态方法或加载该类时创造实例。起到了懒加载(Lazy Loading)的作用。在单线程下安全。
缺点:在多个线程同时访问到该类getInstance()方法时,可能会同时生成多个实例,与我们的目的冲突。


  1. 懒汉式(尝试解决):针对多个线程同时访问产生多个实例的情况做调整优化。
public class SingletonLazy {
	private static SingletonLazy instance;

	private SingletonLazy() {}// 私有化单例类构造器

	public static SingletonLazy getInstance() {
		if (instance == null) {
		// 使用 synchronized 块,加锁防止多线程同时执行
		 	synchronized (SingletonLazy.class) {
			instance = new SingletonLazy();
			}
		}
		return instance; //返回单例类实例
	}
}

这种情况看似利用synchronized()方法解决了多线程同步的问题,但其实和第2种情况一样,如果同时有多个线程进入到if (instance == null)这里,即使下面的内容加了锁,已经同时进入的线程还是会挨个执行创建实例,产生多个实例。
  这种方法并不能解决线程不安全的问题,不可用


  1. 懒汉式(线程安全版本):
public class SingletonLazy {
	private static SingletonLazy instance;

	private SingletonLazy() {}// 私有化单例类构造器

	public static SingletonLazy getInstance() {
		// 使用 synchronized 块,加锁防止多线程同时执行
		 synchronized (SingletonLazy.class) {
			if (instance == null) {
				instance = new SingletonLazy();
			}
		}
		return instance; //返回单例类实例
	}
}

优点:从结果来看已经解决了线程不安全的问题。
缺点:运行效率非常低,多个线程访问时不论是否已经有单例实例都要排队。不推荐使用。


  1. 懒汉式(线程安全改良版):双重检测
public class SingletonLazy {
	private static SingletonLazy instance;

	private SingletonLazy() {}// 私有化单例类构造器

	public static SingletonLazy getInstance() {
		//检查是否有单例实例,在已经有单例实例的情况下直接绕过加锁检测,返回实例对象。
		if (instance == null) {
		// 使用 synchronized 块,加锁防止多线程同时向下执行
			 synchronized (SingletonLazy.class) {
			 //再次检测,保证单个实例。
				if (instance == null) {
					instance = new SingletonLazy();
					}
			}
		}
		return instance; //返回单例类实例
	}
}

优点:保证了单例的前提下提高了运行效率,线程安全且效率较高。
缺点:可能会产生重排序问题。


  理解重排序:
https://www.cnblogs.com/longshiyVip/p/5211476.html
笔者认为这篇博客写的非常详细,读者可自行跳转理解重排序问题,本文不再过多阐述。


  1. 懒汉式(线程安全终极版):volatile关键字
      volatile关键字简直是为了这类问题量身定做的。代码如下
public class SingletonLazy {
	//使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
	private static volatile SingletonLazy instance;

	private SingletonLazy() {}// 私有化单例类构造器

	public static SingletonLazy getInstance() {
		//检查是否有单例实例,在已经有单例实例的情况下直接绕过加锁检测,返回实例对象。
		if (instance == null) {
		// 使用 synchronized 块,加锁防止多线程同时向下执行
			 synchronized (SingletonLazy.class) {
			 //再次检测,保证单个实例。
				if (instance == null) {
					instance = new SingletonLazy();
					}
			}
		}
		return instance; //返回单例类实例
	}
}

  1. 懒汉式:利用静态内部类实现线程安全的延迟加载
public class SingletonLazy {
 
    // 私有内部类,按需加载,用时加载,延迟加载
    private static class Holder {
        private static SingletonLazy instance = new SingletonLazy();
    }
 
    private SingletonLazy() {}
 
    public static SingletonLazy getInstance() {
        return Holder.instance;
    }
}

优点:资源利用率高,而且线程安全。线程安全原理和饿汉式相似。不再过多阐述。
缺点:第一次加载时稍慢。


  1. 枚举:
public enum SingletonEnum {
	INSTANCE;
	public String name;
	public void doSomething() {
		System.out.println("doSomething");
	}
	public static void main(String[] args) {
		SingletonEnum.INSTANCE.doSomething();
	}
}

枚举的创建由JVM保证,所以不会产生并发问题,构造器自动私有,外部不能创建只能引用。
枚举的其余特性笔者暂时不够了解,不在此多做介绍。

总结

本文介绍了饿汉式、多种懒汉式和枚举等单例模式的常用写法。在懒汉式中指出了一些常见的雷区。
最后,推荐使用的几种方法为:饿汉式、懒汉式(线程安全最终版,含volatile关键字)、懒汉式(静态内部类方法)、枚举。

感谢您的阅读,本文仅代表笔者学习单例模式过后的一点总结。如果有误欢迎指出。

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值