一、什么是单例模式
单例模式的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。属于设计模式三大类中的创建型模式
。单例模式具有典型的三个特点
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
类图如下:
单例模式优缺点
- 优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统效率,同时也能够严格控制客户对它的访问。
- 缺点:也正是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,这样扩展起来有一定的困难。
二、如何实现单例模式
常见的单例模式实现方式有五种:饿汉式
、懒汉式
、双重检测锁式
、静态内部类式
和枚举单例
。而在这五种方式中饿汉式
和懒汉式
又最为常见。
1、饿汉式
饿汉式写法是线程安全的,调用效率高。但是不能延时加载。代码如下:
public class SingletonDemo1 { //线程安全的 //类初始化时,立即加载这个对象 private static SingletonDemo1 instance = new SingletonDemo1(); private SingletonDemo1() { } //方法没有加同步块,所以它效率高 public static SingletonDemo1 getInstance() { return instance; } }
由于该模式在加载类的时候对象就已经创建了,所以加载类的速度比较慢,但是获取对象的速度比较快,且是线程安全的。
2、懒汉式
懒汉式写法线程不安全。示例代码:
public class SingletonDemo2 { //线程不安全的 private static SingletonDemo2 instance = null; private SingletonDemo2() { } //运行时加载对象 public static SingletonDemo2 getInstance() { if (instance == null) { instance = new SingletonDemo2(); } return instance; } }
由于该模式是在运行时加载对象的,所以加载类比较快,但是对象的获取速度相对较慢,且线程不安全。如果想要线程安全的话可以加上synchronized
关键字,但是这样会付出惨重的效率代价。
3、双重检测锁
public class SingletonDemo3 { private static volatile SingletonDemo3 instance = null; private SingletonDemo3() { } //运行时加载对象 public static SingletonDemo3 getInstance() { if (instance == null) { synchronized(SingletonDemo3.class){ if(instance == null){ instance = new SingletonDemo3(); } } } return instance; } }
双检锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
由于singleton=new Singleton()
对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile
修饰signleton
实例变量有效,解决该问题。
4、静态内部类
public class Singleton { private Singleton(){ } public static Singleton getInstance(){ return Inner.instance; } private static class Inner { private static final Singleton instance = new Singleton(); } }
静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
5、枚举单例
public enum Singleton { INSTANCE //doSomething 该实例支持的行为 //可以省略此方法,通过Singleton.INSTANCE进行操作 public static Singleton getInstance() { return Singleton.INSTANCE; } }
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上
- 枚举类隐藏了私有的构造器。
- 枚举类的域是相应类型的一个实例对象。
众所周知,单例模式是创建型模式,都会新建一个实例。那么一个重要的问题就是反序列化。当实例被写入到文件到反序列化成实例时,我们需要重写readResolve
方法,以让实例唯一。
private Object readResolve() throws ObjectStreamException{ return singleton; }
三、单例模式的扩展
单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在 ArmyList 中,客户需要时可随机获取,其结构图如图:
在日常场景中有,比如银行柜台有5个窗口,不管你随机去哪个窗口都可以办理业务,但是也就仅仅有5个业务员,实现代码如下:
public class BusinessWindow { private static int maxBusinessWindow = 5; private static ArrayList<String> NoList = new ArrayList<String>(); private static ArrayList<BusinessWindow> businessWindowList = new ArrayList<BusinessWindow>(); private static int currentBusinessWindow = 0; static{ for(int i=0; i<maxBusinessWindow; i++){ businessWindowList.add(new businessWindowList(i+"号窗口")); } } private BusinessWindow(){} private BusinessWindow(String name){ NoList.add(name); } public static BusinessWindow getInstance(){ Random random = new Randow(); currentBusinessWindow = randow.nexInt(maxBusinessWindow); return businessWindowList.get(currentBusinessWindow); } public static void doSomething(){} }
四、常见应用场景
- 网站计数器。
- 项目中用于读取配置文件的类。
- 数据库连接池。因为数据库连接池是一种数据库资源。
- Spring中,每个
Bean
默认都是单例的,这样便于Spring容器进行管理。 - Servlet中
Application
- Windows中任务管理器,回收站。
等等。