导出导入时用Java队列实现资源保护
准备 Excel 基本功能
SQL 代码
-- MySQL dump 10.13 Distrib 8.2.0, for Linux (x86_64)
-- ------------------------------------------------------
-- Server version 8.0.36
DROP DATABASE IF EXISTS `ExcelTest`;
CREATE DATABASE `ExcelTest`;
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `t_pay`
--
DROP TABLE IF EXISTS `t_pay`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `t_pay` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`pay_no` varchar(50) NOT NULL COMMENT '支付流水号',
`order_no` varchar(50) NOT NULL COMMENT '订单流水号',
`user_id` int DEFAULT '1' COMMENT '用户账号ID',
`amount` decimal(8,2) NOT NULL DEFAULT '9.90' COMMENT '交易金额',
`deleted` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='支付交易表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `t_pay`
--
LOCK TABLES `t_pay` WRITE;
/*!40000 ALTER TABLE `t_pay` DISABLE KEYS */;
INSERT INTO `t_pay` (`id`, `pay_no`, `order_no`, `user_id`, `amount`, `deleted`, `create_time`, `update_time`) VALUES
(1, 'pay17203699', '6544bafb424a', 1, 9.90, 0, '2024-07-07 12:20:42', '2024-07-07 12:20:42'),
(3, 'PAY202308190001', 'ORD202308190001', 1, 50.75, 0, '2024-08-22 15:39:19', '2024-08-22 15:39:19'),
(4, 'PAY202308190002', 'ORD202308190002', 2, 99.90, 0, '2024-08-22 15:39:19', '2024-08-22 15:39:19'),
(5, 'PAY202308190003', 'ORD202308190003', 3, 150.00, 0, '2024-08-22 15:39:19', '2024-08-22 15:39:19'),
(6, 'PAY202308190004', 'ORD202308190004', 4, 9.90, 1, '2024-08-22 15:39:19', '2024-08-22 15:39:19'),
(7, 'PAY202308190005', 'ORD202308190005', 5, 75.50, 0, '2024-08-22 15:39:19', '2024-08-22 15:39:19');
/*!40000 ALTER TABLE `t_pay` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Dumping routines for database 'ExcelTest'
--
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2024-08-23 0:30:44
EasyexcelEntity
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Date;
/**
* @Title: EasyexcelEntity
* @Author David
* @Package com.cloud.payment.entities
* @Date 2024/8/22 下午10:40
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "t_pay")
public class EasyexcelEntity {
@Id
@ExcelProperty("支付ID")
@GeneratedValue(generator = "JDBC")
private Integer id;
/**
* 支付流水号
*/
@ExcelProperty("支付流水号")
@Column(name = "pay_no")
private String payNo;
/**
* 订单流水号
*/
@ExcelProperty("订单流水号")
@Column(name = "order_no")
private String orderNo;
/**
* 用户账号ID
*/
@ExcelProperty("用户账号ID")
@Column(name = "user_id")
private Integer userId;
/**
* 交易金额
*/
@ExcelProperty("交易金额")
private BigDecimal amount;
/**
* 删除标志,默认0不删除,1删除
*/
private Byte deleted;
/**
* 创建时间
*/
@ExcelProperty("创建时间")
@Column(name = "create_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 更新时间
*/
@ExcelProperty("更新时间")
@Column(name = "update_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}
EasyexcelMapper
import com.cloud.payment.entities.EasyexcelEntity;
import tk.mybatis.mapper.common.Mapper;
/**
* @Title: EasyexcelMapper
* @Author David
* @Package com.cloud.payment.mapper
* @Date 2024/8/22 下午11:34
*/
public interface EasyexcelMapper extends Mapper<EasyexcelEntity> {
}
EasyexcelMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cloud.payment.mapper.EasyexcelMapper">
<resultMap id="BaseResultMap" type="com.cloud.payment.entities.EasyexcelEntity">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="pay_no" jdbcType="VARCHAR" property="payNo"/>
<result column="order_no" jdbcType="VARCHAR" property="orderNo"/>
<result column="user_id" jdbcType="INTEGER" property="userId"/>
<result column="amount" jdbcType="DECIMAL" property="amount"/>
<result column="deleted" jdbcType="TINYINT" property="deleted"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
</resultMap>
</mapper>
EasyexcelServiceImple
import com.alibaba.excel.EasyExcelFactory;
import com.cloud.payment.entities.EasyexcelEntity;
import com.cloud.payment.mapper.EasyexcelMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Title: EasyexcelServiceImple
* @Author David
* @Package com.cloud.payment.service
* @Date 2024/8/22 下午11:00
*/
@Service
public class EasyexcelServiceImple {
@Resource
private EasyexcelMapper easyexcelMapper;
public void downloadExcel(ServletOutputStream outputStream, String ExcelName) {
EasyExcelFactory.write(outputStream, EasyexcelEntity.class).sheet(ExcelName).doWrite(this::getUserList);
}
public List<EasyexcelEntity> getUserList() {
return easyexcelMapper.selectAll();
}
}
EasyexcelController
import com.cloud.payment.service.impl.EasyexcelServiceImple;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.net.URLEncoder;
/**
* @Title: EasyexcelController
* @Author David
* @Package com.cloud.payment.controller
* @Date 2024/8/22 下午10:38
* @description:
*/
@RestController
@RequestMapping("/DownloadExcel")
public class EasyexcelController {
@Resource
private EasyexcelServiceImple easyexcelServiceImple;
@GetMapping("/getone")
public void downloadoneExcel(@RequestParam("excelname") String ExcelName, HttpServletResponse response) {
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
String filename = URLEncoder.encode(ExcelName + System.currentTimeMillis() + ".xlsx", "UTF-8");
response.setHeader("Content-disposition",
"attachment;filename=" + filename);
easyexcelServiceImple.downloadExcel(response.getOutputStream(), ExcelName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
准备进行互斥功能设计
思考
在设计互斥功能时,主要的考虑点是确保多线程环境下共享资源的安全访问和操作。具体来说,以下几个方面是设计互斥功能时需要关注的关键:
1. 线程安全性
通过使用同步机制(如 synchronized
关键字)来保证多个线程访问共享资源时不会出现数据不一致的问题。在上面的代码中,add
和 get
方法都使用了 synchronized
,以确保在多线程环境下对队列的操作是安全的。
2. 避免死锁
设计时要考虑到避免出现多个线程之间相互等待资源的情况,导致程序无法继续执行。在上面的代码中,合理使用 wait
和 notifyAll
机制,可以有效避免资源竞争和死锁问题。
3. 条件等待与通知机制
在互斥设计中,合理运用条件等待(如 wait()
)和通知(如 notifyAll()
)可以实现线程间的协作。在队列已满或者为空时,线程会进入等待状态,当条件满足时会被唤醒以继续执行,从而保证队列操作的流畅性和正确性。
4. 效率与资源利用率
在保证线程安全的前提下,还需要考虑性能问题,避免频繁地进行上下文切换或长时间占用锁资源。通过控制临界区的粒度和使用合适的锁策略,可以提高系统的整体效率。
5. 灵活的锁机制
可以根据实际需求选择使用不同的锁机制,如可重入锁(ReentrantLock)、读写锁(ReadWriteLock)等,以提升并发场景下的资源利用率和性能。
对于当前问题分析
我们这种需要下载上传的功能CPU上下文的切换所消耗的时间基本上可以忽略不计入,大部分时间消耗会在网络和业务上,所以只需要考虑线程安全、避免死锁。
这种同步的资源可以用队列实现,而java中有很多队列选取LinkedList来实现
实现的代码
User
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @Title: User
* @Author David
* @Package com.cloud.payment.entities
* @Date 2024/9/4 下午12:27
* @description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private LocalDateTime time;
private Thread thread;
}
BaseQueue
写一个父类方便后面子类继承方便扩展功能
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedList;
/**
* @Title: BaseQueue
* @Author David
* @Package com.cloud.payment.entities
* @Date 2024/8/23 下午5:27
*/
@Slf4j
public class BaseQueue<K> {
private final int MAX_SIZE = 10;
private final LinkedList<K> queue;
public BaseQueue() {
this.queue = new LinkedList<>();
}
/**
* 向队列添加元素
*/
public synchronized void add(K k) {
while (queue.size() >= MAX_SIZE) {
try {
log.info("队列已满,正在等待...");
wait(); // 队列满了,当前线程等待
} catch (InterruptedException e) {
log.error("add 方法中断异常: {}", e.getMessage());
Thread.currentThread().interrupt(); // 恢复中断状态
throw new RuntimeException(e);
}
}
queue.add(k);
log.info("添加元素到队列,目前队列大小: {}", queue.size());
notifyAll(); // 唤醒等待的线程
}
/**
* 从队列获取元素
*/
public synchronized K get() {
while (queue.isEmpty()) {
try {
log.info("队列为空,正在等待...");
wait(); // 队列为空,当前线程等待
} catch (InterruptedException e) {
log.error("get 方法中断异常: {}", e.getMessage());
Thread.currentThread().interrupt(); // 恢复中断状态
throw new RuntimeException(e);
}
}
K k = queue.removeFirst();
log.info("从队列中取出元素,目前队列大小: {}", queue.size());
notifyAll(); // 唤醒等待的线程
return k;
}
}
ExportQueue
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* @Title: ExportQueue
* @Author David
* @Package com.cloud.payment.entities
* @Date 2024/8/23 上午10:42
*/
@Slf4j
@Scope("singleton")
@Component
public class ExportQueue<K> extends BaseQueue<K> {
public ExportQueue() {
super();
}
@Override
public synchronized void add(K k) {
super.add(k);
}
@Override
public synchronized K get() {
return super.get();
}
}
修改EasyexcelServiceImple方法
import com.alibaba.excel.EasyExcelFactory;
import com.cloud.payment.entities.EasyexcelEntity;
import com.cloud.payment.entities.ExportQueue;
import com.cloud.payment.entities.User;
import com.cloud.payment.mapper.EasyexcelMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
/**
* @Title: EasyexcelServiceImple
* @Author David
* @Package com.cloud.payment.service
* @Date 2024/8/22 下午11:00
*/
@Slf4j
@Service
public class EasyexcelServiceImple {
@Resource
private EasyexcelMapper easyexcelMapper;
@Resource
private ExportQueue<User> exportQueue;
public void downloadExcel(ServletOutputStream outputStream, String ExcelName) {
exportQueue.add(new User(LocalDateTime.now(), Thread.currentThread()));
EasyExcelFactory.write(outputStream, EasyexcelEntity.class).sheet(ExcelName).doWrite(this::getUserList);
User u = exportQueue.get();
log.info("获取到的元素: {}", u);
}
public List<EasyexcelEntity> getUserList() {
return easyexcelMapper.selectAll();
}
}
测试
使用ApiFox
准备好测试接口以及自动化测试环境
测试成功截图
可以看出下载的资源使用时间比CPU上下文切换的时间更多
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 David
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果