享元模式
定义:提供了减少对象数量从而改善应用所需的对象结构的方式,是对象池的一种实现。最典型的例子就是线程池,线程池可以避免不停地创建和销毁多个对象而导致性能的消耗
宗旨:共享细粒度对象,将多个对同一个对象的访问集中起来
适用场景:系统有大量相似对象、需要缓存池
优点:
- 减少对象的创建,降低内存中对象的数量,提高程序运行效率
- 减少内存之外的资源占用,如:连接池,每一个连接都和数据库有一个TCP连接,占用着网络资源
缺点:
- 需要关注内、外部状态,关注线程安全问题
- 使程序逻辑变得复杂
享元模式的内部状态和外部状态
享元模式的定义提出了两个要求:细粒度和共享对象。因为要求细粒度对象,所以不可避免地会使对象数量多且性质相近,此时我们需要将这些对象的信息分为两个部分
内部状态:存储在享元对象内部并且不会随环境的改变而改变。如数据库连接的用户名、密码、url等信息
外部状态:享元对象得以依赖的一个标记,是随环境的改变而改变的。如每个连接回收利用时,需要给它标记为可用状态
示例
以火车票为例
票据统一接口
java
public interface ITicket {
void showInfo(String bunk);
}火车票的实现
java
public class TrainTicket implements ITicket {
private String from;
private String to;
private int price;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
public void showInfo(String bunk) {
this.price = new Random().nextInt(500);
System.out.println(String.format("%s->%s:%s价格:%s 元", this.from, this.to, bunk, this.price));
}
}票据的创建工厂,用于通过查询返回票据
java
public class TicketFactory {
private static Map<String, ITicket> sTicketPool = new ConcurrentHashMap<String,ITicket>();
public static ITicket queryTicket(String from, String to) {
String key = from + "->" + to;
if (TicketFactory.sTicketPool.containsKey(key)) {
System.out.println("使用缓存:" + key);
return TicketFactory.sTicketPool.get(key);
}
System.out.println("首次查询,创建对象: " + key);
ITicket ticket = new TrainTicket(from, to);
TicketFactory.sTicketPool.put(key, ticket);
return ticket;
}
}这个写法和注册式单例没有太大的区别,但是注册式单例侧重的是创建对象,而享元模式侧重的是整个票据管理的结构,达到资源重复利用的目的
享元模式在源码中的体现
String中的享元模式
Java中将 String 类定义为 final (不可变),JVM 中字符串一般保存在字符串常量池中,确保一个字符串在常量池中只有一个拷贝
Integer中的享元模式
Integer 中的 valueOf() 方法做了一个条件判断,如果目标值在 -128 到 127 之间,就直接从缓存中取值,否则新建对象。这么做的原因可能是认为 -128 ~ 127 这个范围的数据使用是最为频繁的,为了节省频繁创建对象带来的内存消耗
java
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}