消费者模式小例子
生产消费者模式是日常开发中常用的设计模式之一,最为关键的作用呢,其实就是将一个长过程拆分为两部分或者多部分异步完成。设计模式更符合面向对象的思想。
举个反面例子:
下面这张图真**丑🤮,不过也是日常开发中的常态,这种编程风格近乎于面向过程开发。
举个反面例子:
下面这张图真**丑🤮,不过也是日常开发中的常态,这种编程风格近乎于面向过程开发。

客户端在发起一次请求之后,后台需要记录本次用户的请求记录,对于客户端来说,记录日志的操作客户端是不需要感知到的,客户端只在乎返回结果。记录log完全是服务端自己的事情。
主要的缺点:
1.对于用户不需要感知的操作却完全运行在客户端请求-响应的线程上。
2.每一次访问产生的一条sql都需要去独立io数据库,挺浪费的。
3.不符合面向对象的编程思想,修改log业务代码时其实就是在修改主流程业务的代码。
怎么优化呢?
我们可以把记录log的操作剥离出来,不占用用户请求-响应的这条线程,并且将记录log的任务放在一个缓冲区里,批量持久化到数据库中。这样一来似乎合理了很多。

代码设计如下:
LogService 接口设计
public interface LogService{
/**
* 对外提供一个添加任务的方法
* @param e
*/
void add(LogEntity e);
}
LogServiceImpl 具体实现
@Slf4j
@Service
public class LogServiceImpl extends ServiceImpl<LogMapper, LogEntity> implements LogService {
/**
* 批量保存的大小
*/
private static final int BATCH_SAVE_SIZE = 200;
/**
* 日志任务队列
*/
private static final BlockingQueue<LogEntity> QUEUE = new LinkedBlockingQueue<>();
/**
* 单一线程池,具体的消费者
*/
private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
@PostConstruct
public void init(){
//启动后台任务
EXECUTOR_SERVICE.submit(this::backEndTask);
}
@Override
public void add(LogEntity e) {
if (Objects.isNull(e)) {
return;
}
if (QUEUE.offer(e)) {
log.error("添加任务失败,{}", e);
}
}
private void backEndTask() {
final List<LogEntity> logTasks = new ArrayList<>(BATCH_SAVE_SIZE);
while (true) {
//检索并删除此队列的头,如果此队列为空,则返回null
LogEntity logTask = QUEUE.poll();
//如果不是null,将任务添加进集合
if (!Objects.isNull(logTask)) {
logTasks.add(logTask);
}
//如果是空表示队列中没有任务了,或者集合的容量已经满了,就批量保存
if (Objects.isNull(logTask) || logTasks.size() == BATCH_SAVE_SIZE) {
saveAll(logTasks);
logTasks.clear();
try {
//检索并删除此队列的头,必要时等待,直到某个元素变为可用。
logTasks.add(QUEUE.take());
} catch (InterruptedException e) {
logTasks.clear();
log.error("写入异常", e);
}
}
}
}
private void saveAll(List<LogEntity> logs) {
if (logs == null || logs.size() == 0) {
return;
}
super.saveBatch(logs);
}
}
关于持久化部分功能结合了mybatis的增强插件mybatis-plus,具体的逻辑运用了阻塞队列的特性。不足的地方还请多多指教。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)