设计模式|单例模式
在软件开发过程中经常会遇到一个类只需要一个对象, 我们在整个程序的运行过程供仅使用这个对象完成一部分功能, 这种类的设计有一个传统的名字——单例模式. 单例模式体现了功能的高内聚, 单例独自拥有一个系统的一部分功能, 通过单例对象在系统的任何地方调用方法也就统一管理的系统的这部分功能.
单例在前后端用到都很多. 从前端来说, js 中 window 对象 document 对象都是某个网页的单例, 他们集中管理的网页窗口和网页功能的 API, Vue 中 vuex 也是单例的; 从后端来说, 连接池对象可以是单例的, 网站计数器也是单例的.
下面介绍三种经典的单例。
懒汉单例
懒汉单例的对象在类被加载的时候就会被初始化好, 这样会避免多线程时多创建对象问题, 不好的一点是无论你用没有用这个类(只要一导入 import)它就初始化了, 因此相比于饿汉单例在导入且不使用的时候浪费存储空间.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
System.out.println("Singleton instance has init.");
}
public static Singleton getInstance(){
return instance;
}
public void find(){
//do operate
System.out.println("you do a find operate.");
}
}
饿汉单例
一 饿汉单例在第一次使用进行初始化, 这种初始化在多线程环境下会多创建对象, 这是不符合单例模式的逻辑的(虽然在之后多次调用返回同一个对象), 解决方法时加锁.
/**
* 单例-饿汉
* getInstance() 方法在多线程环境下可能会出现线程安全问题
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("Singleton instance has init.");
}
/**
* 线程不安全
*/
public static Singleton getInstance() {
if(null == instance){
instance = new Singleton();
}
return instance;
}
public void find() {
//do operate
System.out.println("you do a find operate.");
}
}
二
对方法加锁, 采用双 if 判断保障仅初始化一个对象. 如果用一个 if 仍不能保证只创建一个对象, 这时需要双 if 判断. 当多条线程同时进入if
语句, 一个线程拿到锁, 其他线程进入阻塞状态, 如果在锁中不加以判断 instance 是否为空, 其他线程拿到锁仍会创建对象.
/**
* 单例-饿汉 线程安全
*/
public class Singleton2 {
private static Singleton2 instance;
private static int count = 0;
private static Object lock = new Object();
private Singleton2() {
System.out.println("Singleton2 instance has init.---" + (++count));
}
/**
* 加锁, 双重if, 保证只初始化一个对象
*/
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (lock) {
if(instance==null){
instance = new Singleton2();
}
}
}
return instance;
}
public void find() {
//do operate
System.out.println("you do a find operate.");
}
}
枚举单例
枚举是通过 jvm 的机制来保证使用的时候对象已被初始化且只有一个对象, 这个可以确保在多线程环境下只初始化一个对象. 这种单例是《Effective Java》所提倡的。
public enum EnumSingleton {
instance();
EnumSingleton(){
System.out.println("EnumSingleton has init.");
}
public void find() {
//do operate
System.out.println("you do a find operate.");
}
}
总结
简单总结下使用单例的情况:
- 当类只能有一个实例而且第三方可以从一个公共的访问加点访问他时
- 当一个唯一的实例可以通过子类话来扩展, 而且第三方要在不更改代码的情况下就能使用一个扩展的实例时