在基金业务中,基金数量非常多,查询起来非常慢,优化方案是系统每天从数据库把所有的基金信息取出并缓存起来,用户查询的时候直接从缓存里取。而基金缓存信息每天都需要定时更新。
传统的技术方案如下:
图 传统技术方案
上述方案有个严重问题,就是FundCacheUtil可以被创建多个实例,这样既会给系统加重负担,同时在不同的FundCacheUtil的实例中,获取的基金信息可能会不一致。
当然,也可以把FundCacheUtil的cacheList设置为静态变量,其方法都设置为静态方法。这样,就能保证系统只存在一个cacheList了。
public class FundCacheUtils {
private static List<Map<String,Object>> cacheList;
private FundCacheUtils() {}
private static void updateCache() {
//每天7点定时更新
cacheList = new ArrayList<>();
}
public static List<Map<String,Object>> getCacheList() {
synchronized (FundCacheUtils.class) {
if (cacheList == null) {
updateCache();
}
}
return cacheList;
}
}
public class Client {
public List<Map<String,Object>> searchFundList() {
return FundCacheUtils.getCacheList();
}
public static void main(String[] args) {
Client client = new Client();
List<Map<String, Object>> list1 = client.searchFundList();
List<Map<String, Object>> list2 = client.searchFundList();
System.out.println(list1 == list2);
}
}
这段代码虽然保证了系统只存在一个cacheList,但也存在一个问题:跟新缓存(updateCache)的操作不应该出现在getCacheList中,而是需要确保在调用getCacheList方法之前,已经执行完了updateCache方法。
1 单例模式概述
确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
图 单例模式
Singleton: 单例类,只有一个实例。对外提供getInstance()方法来向整个系统提供这个实例。
public class FundCacheStorage {
volatile private static FundCacheStorage instance = null;
private List<Map<String,Object>> cacheList;
private FundCacheStorage() {
updateCacheList();
}
private void updateCacheList() {
// 每天7点执行一次
cacheList = new ArrayList<>();
}
public List<Map<String,Object>> getCacheList() {
return this.cacheList;
}
public static FundCacheStorage getInstance() {
synchronized (FundCacheStorage.class) {
if (instance == null) instance = new FundCacheStorage();
}
return instance;
}
}
public class Client {
public static void main(String[] args) {
FundCacheStorage instance1 = FundCacheStorage.getInstance();
List<Map<String, Object>> cacheList1 = instance1.getCacheList();
FundCacheStorage instance2 = FundCacheStorage.getInstance();
List<Map<String, Object>> cacheList2 = instance2.getCacheList();
System.out.println(cacheList2 == cacheList1);
}
}
1.1 饿汉式与懒汉式
饿汉式:定义静态变量的时候实例化单例类,在类加载时自行实例化。
private static Singleton instance = new Singleton();
懒汉式:在类加载时并不自行实例化,在需要的时候再加载实例。这种技术又称为延迟加载(Lazy Load)技术。
懒汉式获取实例的时候,需要使用双重检测锁定,来避免多线程会创建不同的实例。
模式 | 优点 | 缺点 |
饿汉式 |
|
|
懒汉式 | 1)在第一次使用时才创建,不会一直占用系统资源。 | 1)必须处理好多线程同时访问问题。 |
表 饿汉式与懒汉式优缺点
1.2 按需初始化持有者(IoDH)
全称为Initialization on Demand Holder,简称为IoDh技术,实现IoDH时,需在单例类中增加一个静态内部类,在该内部内中创建实例对象,再将该实例对象通过getInstance()方法返回给外部使用。
public class FundCacheStorage2 {
private List<Map<String,Object>> cacheList;
private FundCacheStorage2() {}
private static class HolderClass {
private final static FundCacheStorage2 instance = new FundCacheStorage2();
}
public static FundCacheStorage2 getInstance() {
return HolderClass.instance;
}
private void updateCacheList() {
// 每天7点执行一次
cacheList = new ArrayList<>();
}
public List<Map<String,Object>> getCacheList() {
return this.cacheList;
}
}
IoDH 技术由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有被任何线程锁定,因此其性能不会造成任何影响。
通过使用IoDH,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。因此,IoDH不失为一种最好的Java语言单例模式的实现方式。但是该技术与编程语言本身的特性相关,很多面向对象语言不支持IoDH.
2 优缺点
优点:
- 单例模式提供了对唯一实例的受控访问。它可以严格控制客户怎样以及何时访问它。
- 系统内存中只存在一个对象,可以节约系统资源。
3)允许可变数目的实例,基于单例模式,开发者可进行扩展,来获得指定个数的实例对象。即节省系统资源,又解决了由于单例对象共享过多有损性能的问题。
缺点:
- 单例模式职责过重,既提供了业务方法,又提供了创建对象的方法。
- 如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自定销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态丢失。
3 适用场景
- 系统只需一个实例对象。
- 实例对象需要在系统中提供共享内容。