个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道
如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充
前言
对于后端开发者而言,池化技术相当常见,比如线程池、数据库连接池、缓冲池,以及最最常见的Bean
池
对于Bean
池,在系统初始化的时候初始化装载Bean对象,系统中需要使用的时候直接调用Bean
池中的对象即可,而无需每次都去初始化一遍,以节省资源消耗
针对String
的性能优化,Java
引用了缓存池的概念,即创建String
类型数据的时候,会检查缓存池中是否有相同内容的String
类型对象,如果有会被直接引用,没有则会创建一个存入缓存池,从而避免重复创建String
对象的无谓资源消耗
Integer
型数据默认会有-128~127
的缓存池,以规避这些高频率出现的数据的重复创建过程,节省资源
享元(Flyweight)模式运用共享技术,通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
1.介绍
使用目的:运用共享技术来复用需要重复使用的资源
使用时机:系统中需要使用大量对象,且系统不依赖这些对象的身份,且这些对象可以分组,每组都可以用一个对象来代替
解决问题:系统中存在大量相同的对象,造成无谓的资源消耗,甚至造成内存溢出
实现方法:用 HashMap
存储这些对象,并使用唯一标识标记,对于不存在的对象创建并存入,已存在的则直接取出使用
应用实例:
Java
中的String
缓存- 池化技术,如线程池、连接池、bean池等
优点:减少了系统的资源消耗,降低了系统压力,提高执行效率
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
注意事项:
- 注意划分外部状态和内部状态,否则容易引起线程安全问题
- 这些对象最好由工厂控制
2.结构
享元模式的主要角色有如下。
- 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口。
- 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
- 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
- 客户端(client)调用享元工厂(Flyweight Factory)角色中的方法
- 享元工厂通过
HashMap
结构持有具体享元(Concrete Flyweight)角色对象 - 具体享元角色则需要实现接口Flyweight
此图中未包括复杂数据的存取,请按照实际需求调整,实际上的数据肯定更加复杂
3.实现
-
定义抽象享元角色(Flyweight)
interface Flyweight { void operation(); }
-
定义具体享元(Concrete Flyweight)角色,实现接口Flyweight
class ConcreteFlyweight implements Flyweight { private String key; ConcreteFlyweight(String key) { this.key = key; System.out.println("具体享元" + key + "被创建!"); } public void operation() { System.out.println("具体享元" + key + "被调用,"); } }
-
定义享元工厂(Flyweight Factory)角色,管理Concrete Flyweight
class FlyweightFactory { private HashMap<String, Flyweight> flyweights = new HashMap<>(); public Flyweight getFlyweight(String key) { Flyweight flyweight = (Flyweight) flyweights.get(key); if (flyweight != null) { // System.out.println("具体享元" + key + "已经存在,被成功获取!"); } else { flyweight = new ConcreteFlyweight(key); flyweights.put(key, flyweight); } return flyweight; } }
完整代码
package com.company.test.flyweight;
import java.util.HashMap;
interface Flyweight {
void operation();
}
class ConcreteFlyweight implements Flyweight {
private String key;
ConcreteFlyweight(String key) {
this.key = key;
System.out.println("具体享元" + key + "被创建!");
}
public void operation() {
System.out.println("具体享元" + key + "被调用,");
}
}
class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
Flyweight flyweight = (Flyweight) flyweights.get(key);
if (flyweight != null) {
// System.out.println("具体享元" + key + "已经存在,被成功获取!");
} else {
flyweight = new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
}
return flyweight;
}
}
public class FlyweightTest {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
System.out.println("------------------------------------------ 分别引用享元a,b ------------------------------------------");
factory.getFlyweight("a").operation();
factory.getFlyweight("b").operation();
System.out.println("------------------------------------------ 分别引用享元a,b,c ------------------------------------------");
factory.getFlyweight("a").operation();
factory.getFlyweight("b").operation();
factory.getFlyweight("c").operation();
}
}
运行结果
- 第一轮调用享元a,b时,均需要创建对象再引用
- 第二轮调用享元a,b时不再创建,而是直接引用,但对于c依然需要创建后引用
- 同理,之后如果再次调用享元a,b,c也不需要创建,而是直接引用即可
4.模拟示例
为了更加贴近我们实际使用的情况,模拟一个下载管理器
临时手写的,肯定不够完整,仅写出其中主要逻辑,其中下载逻辑未补全
package com.company.test.flyweight;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 任务接口
*/
interface DownloadTask {
void startDownload();
void stopDownload() throws InterruptedException;
}
/**
* 公共任务虚拟类
*/
abstract class CommonTask implements DownloadTask {
protected String fileName;
protected int progress;
protected boolean isAlive;
protected Thread thread;
@Override
public void stopDownload() {
//todo 仅做暂停任务,如果要删除任务,请先暂停,不然可能导致线程问题
isAlive = false;
System.out.println(new Date().toString() + ": " + fileName + " " + "downLoad stop!");
}
@Override
public void startDownload() {
//已下载完成,不再继续下载
if (progress >= 100) {
//todo 这里仅做提示,实际应用中可以添加其他逻辑
System.out.println(new Date().toString() + ": " + fileName + " " + "has been downloaded!");
return;
}
System.out.println(new Date().toString() + ": " + fileName + " " + "downLoad start!");
//正在下载中,不需要重建线程
if (!isAlive) {
isAlive = true;
thread = createThread();
thread.start();
}
}
protected Thread createThread() {
return new Thread(() -> {
while (isAlive && progress < 100) {
//todo 这里只做模拟进度,实际进度请自行扩展,不做赘述
progress += 20;
System.out.println(new Date().toString() + ": " + fileName + " " + progress + "%");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (progress >= 100) {
isAlive = false;
System.out.println(new Date().toString() + ": " + fileName + " " + "has bean downLoaded successfully!");
}
});
}
}
class VideoTask extends CommonTask {
//todo video类型的特有逻辑
public VideoTask(String fileName) {
this.fileName = fileName;
this.progress = 0;
this.isAlive = false;
}
}
class PicTask extends CommonTask {
//todo picture类型的特有逻辑
public PicTask(String fileName) {
this.fileName = fileName;
this.progress = 0;
this.isAlive = false;
}
}
/**
* 下载管理器
*/
class DownloadManager {
/**
* 任务池
*/
Map<String, DownloadTask> tasks = new HashMap<>();
/**
* 获取任务,或者创建任务
*/
public DownloadTask getTask(String fileName) {
DownloadTask task = tasks.get(fileName);
if (task == null) {
if (fileName.endsWith(".mp3")) {
task = new VideoTask(fileName);
} else {
task = new PicTask(fileName);
}
tasks.put(fileName, task);
}
return task;
}
}
public class DownloadTest {
public static void main(String[] args) throws InterruptedException {
DownloadManager downloadManager = new DownloadManager();
System.out.println("------------------------------------------ 第一轮测试 ------------------------------------------");
//获取任务,并开始下载
DownloadTask task1 = downloadManager.getTask("你的酒馆对我打了烊.mp3");
task1.startDownload();
//三秒后暂停下载
Thread.sleep(3000);
task1.stopDownload();
System.out.println("------------------------------------------ 第二轮测试 ------------------------------------------");
//三秒再次获取任务并下载
Thread.sleep(3000);
DownloadTask task2 = downloadManager.getTask("你的酒馆对我打了烊.mp3");
task2.startDownload();
//获取新任务并下载
DownloadTask task3 = downloadManager.getTask("土拨鼠尖叫.jpg");
task3.startDownload();
System.out.println("------------------------------------------ 第三轮测试 ------------------------------------------");
//三秒后重新开始两项任务
Thread.sleep(3000);
DownloadTask task4 = downloadManager.getTask("你的酒馆对我打了烊.mp3");
task4.startDownload();
DownloadTask task5 = downloadManager.getTask("土拨鼠尖叫.jpg");
task5.startDownload();
//维持主线程
while (true) {
}
}
}
运行结果
- 第一轮测试中新建mp3下载,并在三秒后终止,结束时下载进度为60%
- 第二轮再次启动mp3的下载,并新建jpg下载
- 第三轮先等待三秒再启动下载
- mp3已下载完成,提示已下载
- jpg仍在下载中,则继续下载,直至下载完成
仅供参考池化逻辑,实际应用中的下载管理器比这要复杂多了,但依然需要池化管理
5.其他问题
5.1.为什么需要池化管理
节省系统资源,降低内存消耗,加快执行速度
比如上面示例中,不使用池化管理,则每次获取任务都需要重建,造成不必要的消耗,且会丢失已下载进度
作者:Echo_Ye
WX:Echo_YeZ
Email :echo_yezi@qq.com
个人站点:在搭了在搭了。。。(右键 - 新建文件夹)
5.2.如何共享
在合适的地方初始化享元工厂即可,如果想在多个类中公用,也可以将其丢进bean池,或者单例模式轻松解决,Android也可以使用服务
5.3.池化一定需要享元工厂吗?是否可以将池放在客户端里?
当然可以,实际上很多地方也是这么做的,但是封装性不够好,且不便于与其他客户端共享
通常如果客户端里定义享元工厂,会将类封装起来,不将池暴露出去,达到类似的效果
别的想起来再写吧。。。
后记
实际开发中经常使用到池化管理,算是常用的实战技巧了,享元模式只是更加规范化的整理出来而已
作者:Echo_Ye
WX:Echo_YeZ
Email :echo_yezi@qq.com
个人站点:在搭了在搭了。。。(右键 - 新建文件夹)