享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
单例模式是类级别的,一个类只能有一个对象实例;享元模式是对象级别的,可以有多个对象实例,多个变量引用同一个对象实例;享元模式主要是为了节约内存空间,提高系统性能,而单例模式主要为了可以共享数据;
使用场景:自定义线程池或者某些由固定参数(不同值)生成的DTO;以下是自定义线程池,核心参数配置在数据库;工厂方法类维护一个对象存储池。
public class WorkExecutorRepository {
/**
* final修饰引用类型成员变量表示引用地址不可变,其内容可以改变
*/
private final ConcurrentHashMap executors;
private WorkExecutorRepository() {
executors = new ConcurrentHashMap();
}
/**
* 延迟初始化单例模式
*/
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 workExecutorProfile = workExecutorDAO.getWorkManagementProfile();
for (WorkExecutorDTO workExecutorDTO : (Iterable) 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(workExecutorDTO.getMaximumWorkQueueSize()));
}
else if (workExecutorDTO.getMaximumWorkQueueSize() == 0) {
return new TrackingThreadPool(0, workExecutorDTO.getMaximumPoolSize(), 60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
else if (workExecutorDTO.getMaximumWorkQueueSize() < 0) {
return new TrackingThreadPool(workExecutorDTO.getCorePoolSize(), workExecutorDTO.getMaximumPoolSize(), 0L,
TimeUnit.SECONDS, new LinkedBlockingQueue());
}
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 workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
private final Map inProgress = new ConcurrentHashMap();
private final ThreadLocal startTime = new ThreadLocal();
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 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 {
/**
* final修饰引用类型成员变量表示引用地址不可变,其内容可以改变
*/
private final ConcurrentHashMap map;
private FlyweightRepository() {
map = new ConcurrentHashMap();
}
/**
* 延迟初始化单例模式
*/
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 pool = new HashMap();
// 因为内部状态具备不变性,因此作为缓存的键
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 sTicketPool = new ConcurrentHashMap();
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 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(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 设计模式-享元模式