Skip to content

享元模式

定义:提供了减少对象数量从而改善应用所需的对象结构的方式,是对象池的一种实现。最典型的例子就是线程池,线程池可以避免不停地创建和销毁多个对象而导致性能的消耗

宗旨:共享细粒度对象,将多个对同一个对象的访问集中起来

适用场景:系统有大量相似对象、需要缓存池

优点:

  1. 减少对象的创建,降低内存中对象的数量,提高程序运行效率
  2. 减少内存之外的资源占用,如:连接池,每一个连接都和数据库有一个TCP连接,占用着网络资源

缺点:

  1. 需要关注内、外部状态,关注线程安全问题
  2. 使程序逻辑变得复杂

享元模式的内部状态和外部状态

享元模式的定义提出了两个要求:细粒度和共享对象。因为要求细粒度对象,所以不可避免地会使对象数量多且性质相近,此时我们需要将这些对象的信息分为两个部分

内部状态:存储在享元对象内部并且不会随环境的改变而改变。如数据库连接的用户名、密码、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);
}