设计模式——享元模式

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

 单例模式是类级别的,一个类只能有一个对象实例;享元模式是对象级别的,可以有多个对象实例,多个变量引用同一个对象实例;享元模式主要是为了节约内存空间,提高系统性能,而单例模式主要为了可以共享数据;

使用场景:自定义线程池或者某些由固定参数(不同值)生成的DTO;以下是自定义线程池,核心参数配置在数据库;工厂方法类维护一个对象存储池。

public class WorkExecutorRepository {

    /**
     * final修饰引用类型成员变量表示引用地址不可变,其内容可以改变
     */
    private final ConcurrentHashMap<String, ThreadPoolExecutor> executors;

    private WorkExecutorRepository() {
        executors = new ConcurrentHashMap<String, ThreadPoolExecutor>();
    }

    /**
     * 延迟初始化单例模式
     */
    private static class SingletonHandler {
        final static WorkExecutorRepository INSTANCE = new WorkExecutorRepository();
    }

    public static WorkExecutorRepository getInstance() {
        return SingletonHandler.INSTANCE;
    }

    /**
     * 查找已创建的Executor
     */
    public ThreadPoolExecutor lookup(String workExecutorName) {
        return executors.get(workExecutorName);
    }

    /**
     *创建Executor
     */
    public ThreadPoolExecutor bind(String workExecutorName, ThreadPoolExecutor exec) {
        // 不覆盖已有Executor
        return executors.putIfAbsent(workExecutorName, exec);
    }

    /**
     *移除Executor
     */
    public ExecutorService unBind(String workExecutorName) {
        return executors.remove(workExecutorName);
    }
}
@Component("workExecutorFactory")
public class WorkExecutorFactory {

    @Autowired
    private WorkExecutorDAO workExecutorDAO;

    private final static WorkExecutorRepository wr = WorkExecutorRepository.getInstance();

    @PostConstruct
    private void initWorkExecutors() {
        List<WorkExecutorDTO> workExecutorProfile = workExecutorDAO.getWorkManagementProfile();
        for (WorkExecutorDTO workExecutorDTO : (Iterable<WorkExecutorDTO>) workExecutorProfile) {
            ThreadPoolExecutor tpe = initThreadPool(workExecutorDTO);
            wr.bind(workExecutorDTO.getWorkExecutorName(), tpe);
        }
    }

    private ThreadPoolExecutor initThreadPool(WorkExecutorDTO workExecutorDTO) {
        if (workExecutorDTO.getMaximumWorkQueueSize() > 0) {
            return new TrackingThreadPool(workExecutorDTO.getCorePoolSize(), workExecutorDTO.getMaximumPoolSize(), 0L,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(workExecutorDTO.getMaximumWorkQueueSize()));
        }
        else if (workExecutorDTO.getMaximumWorkQueueSize() == 0) {
            return new TrackingThreadPool(0, workExecutorDTO.getMaximumPoolSize(), 60L, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
        }
        else if (workExecutorDTO.getMaximumWorkQueueSize() < 0) {
            return new TrackingThreadPool(workExecutorDTO.getCorePoolSize(), workExecutorDTO.getMaximumPoolSize(), 0L,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        }
        else {
            throw new IllegalArgumentException("无法创建此类型线程池 : " + workExecutorDTO.getWorkExecutorName());
        }
    }

    public static ExecutorService getWorkExecutor(String workExecutorName) {
        return wr.lookup(workExecutorName);
    }
}
public class TrackingThreadPool extends ThreadPoolExecutor {

    public TrackingThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
        BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    private final Map<Runnable, Boolean> inProgress = new ConcurrentHashMap<Runnable, Boolean>();

    private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();

    private long totalTime;

    private int totalTask;

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        startTime.set(Long.valueOf(System.currentTimeMillis()));
        inProgress.put(r, true);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        long time = System.currentTimeMillis() - startTime.get().longValue();
        synchronized (this) {
            totalTime += time;
            ++totalTask;
        }
        super.afterExecute(r, t);
        inProgress.remove(r);
    }

    public Set<Runnable> getInProgressTasks() {
        return Collections.unmodifiableSet(inProgress.keySet());
    }

    public synchronized int getInProgressTaskAmount() {
        return inProgress.keySet().size();
    }

    public synchronized int getTotalTasks() {
        return totalTask;
    }

    public synchronized long getTotalTaskTime() {
        return totalTime;
    }

    public synchronized double getAverageTaskTime() {
        return (totalTask == 0) ? 0 : totalTime / (double) totalTask;
    }

    public synchronized int getQueueSize() {
        return getQueue().size();
    }

}

享元工厂泛型模式:

/**
 * @author: tiger
 * @date: 2021/4/29 16:42
 * @description: 享元工厂
 */
public class FlyweightRepository<K, V> {

    /**
     * final修饰引用类型成员变量表示引用地址不可变,其内容可以改变
     */
    private final ConcurrentHashMap<K, V> map;

    private FlyweightRepository() {
        map = new ConcurrentHashMap<K, V>();
    }

    /**
     * 延迟初始化单例模式
     */
    private static class SingletonHandler {
        final static FlyweightRepository INSTANCE = new FlyweightRepository();
    }

    public static FlyweightRepository getInstance() {
        return SingletonHandler.INSTANCE;
    }

    /**
     * 查找已创建的对象
     */
    public V lookup(K v) {
        return map.get(v);
    }

    /**
     * 创建对象
     */
    public V bind(K k, V v) {
        // 不覆盖已有对象
        return map.putIfAbsent(k, v);
    }

}

其他例子

抽象享元角色(IFlyweight):享元对象抽象基类或者接口,同时定义出对象的外部状态和内部状态的接口或者实现;

// 抽象享元角色
public interface IFlyweight {
    void operation(String extrinsicState);
}

具体享元角色(ConcreteFlyweight):实现抽象角色定义的业务。该角色的内部状态处理应该与环境无关,不能出现会有一个操作改变内部状态 ,同时修改了外部状态;

// 具体享元角色
public class ConcreteFlyweight implements IFlyweight {
    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }


    public void operation(String extrinsicState) {
        System.out.println("Object address: " + System.identityHashCode(this));
        System.out.println("IntrinsicState: " + this.intrinsicState);
        System.out.println("ExtrinsicState: " + extrinsicState);
    }
}

享元工厂(FlyweightFactory):负责管理享元对象池和创建享元对象。

// 享元工厂
public class FlyweightFactory {
    // 考虑并发安全可以用线程安全的Map
    private static Map<String, IFlyweight> pool = new HashMap<String, IFlyweight>();

    // 因为内部状态具备不变性,因此作为缓存的键
    public static IFlyweight getFlyweight(String intrinsicState) {
        if (!pool.containsKey(intrinsicState)) {
            IFlyweight flyweight = new ConcreteFlyweight(intrinsicState);
            pool.put(intrinsicState, flyweight);
        }
        return pool.get(intrinsicState);
    }
}

 测试Test:

public class Test {
    public static void main(String[] args) {
        IFlyweight flyweight1 = FlyweightFactory.getFlyweight("aa");
        IFlyweight flyweight2 = FlyweightFactory.getFlyweight("bb");
        flyweight1.operation("a");
        flyweight2.operation("b");
    }
}

享元模式实现共享池业务:

下面我们举个例子,我们每年春节为了抢到一张回家的火车票都要大费周折,进而出现了很多刷票软件,刷票软件会将我们填写的信息缓存起来,然后定时检查余票信息。抢票的时候,我们肯定是要查询下有我们需要的票信息,这里我们假设一张火车的信息包含:出发站,目的站,价格,座位类别。现在需要编写一个查询火车票查询伪代码,可以通过出发站,目的站查到相关票的信息。
比如要求通过出发站,目的站查询火车票的相关信息,那么我们只需构建出火车票类对象,然后提供一个查询出发站,目的站的接口给到客户进行查询即可,具体代码如下,创建ITicket接口:

public interface ITicket{
    void showInfo(String bunk);
}

然后,创建TrainTicket接口:

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));
    }
}

最后创建TicketFactory类:

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;
    }
}

编写客户端代码:

public class Test {

    public static void main(String[] args) {
        ITicket ticket = TicketFactory.queryTicket("北京西", "长沙");
        ticket.showInfo("硬座");
        ticket = TicketFactory.queryTicket("北京西", "长沙");
        ticket.showInfo("软座");
        ticket = TicketFactory.queryTicket("北京西", "长沙");
        ticket.showInfo("硬卧");
    }
}

分析上面的代码,我们发现客户端进行查询时,系统通过TicketFactory缓存该票对象,然后重复提供给其他查询请求,这样一个对象就足以支撑数以千计的查询请求,对内存完全无压力,使用享元模式可以很好地解决这个问题。除了第一次查询创建对象后,后续查询相同车次票信息都是使用缓存对象,无需创建新对象了。
其中ITcket就是抽象享元角色,TrainTicket就是具体享元角色,TicketFactory就是享元工厂。有些小伙伴一定会有疑惑了,这不就是注册式单例模式吗?对,这就是注册时单例模式。虽然,结构上很像,但是享元模式的重点在结构上,而不是在创建对象上。后面看看享元模式在JDK源码中的一个应用,大家应该就能彻底清楚明白了。
再比如,我们经常使用的数据库连接池,因为我们使用Connection对象时主要性能消耗在建立连接和关闭连接的时候,为了提高Connection在调用时的性能,我们将Connection对象在调用前创建好缓存起来,用的时候从缓存中取值,用完再放回去,达到资源重复利用的目的。来看下面的代码:
 

public class ConnectionPool {

    private Vector<Connection> pool;

    private String url = "jdbc:mysql://localhost:3306/test";
    private String username = "root";
    private String password = "root";
    private String driverClassName = "com.mysql.jdbc.Driver";
    private int poolSize = 100;

    public ConnectionPool() {
        pool = new Vector<Connection>(poolSize);

        try{
            Class.forName(driverClassName);
            for (int i = 0; i < poolSize; i++) {
                Connection conn = DriverManager.getConnection(url,username,password);
                pool.add(conn);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public synchronized Connection getConnection(){
        if(pool.size() > 0){
            Connection conn = pool.get(0);
            pool.remove(conn);
            return conn;
        }
        return null;
    }

    public synchronized void release(Connection conn){
        pool.add(conn);
    }
}

测试类的编写:

public class Test {
    public static void main(String[] args) {
        ConnectionPool pool = new ConnectionPool();
        Connection conn = pool.getConnection();
        System.out.println(conn);
    }
}

源码分析,基本数据包装类的拆箱和装箱,比如Integer:

public static Integer valueOf(int i) {
    // 如果i大于-128,小于等于127
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        // 从缓存中取出对应的Integer对象,这里用的就是享元模式
        return IntegerCache.cache[i + (-IntegerCache.low)];
    // 封装为一个Integer对象
    return new Integer(i);
}
// Integer的构造函数,内部维护了一个int类型变量,保存真实的int值
public Integer(int value) {
    this.value = value;
}
// Integer的equals方法
public boolean equals(Object obj) {
    // 如果是一个Integer对象,则比较
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    //如果根本就不是Integer对象,直接返回false
    return false;
}
// intValue其实就是返回Integer对象内部的int类型的value
// 所以Integer的equals方法其实就是将内部的int类型的value进行了==比较
public int intValue() {
    return value;
}

参考:

享元模式与单例模式区别

java 设计模式-享元模式 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值