准备 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 关键字)来保证多个线程访问共享资源时不会出现数据不一致的问题。在上面的代码中,addget 方法都使用了 synchronized,以确保在多线程环境下对队列的操作是安全的。

2. 避免死锁

设计时要考虑到避免出现多个线程之间相互等待资源的情况,导致程序无法继续执行。在上面的代码中,合理使用 waitnotifyAll 机制,可以有效避免资源竞争和死锁问题。

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

准备好测试接口以及自动化测试环境

20240904123521.png

测试成功截图

20240904123355.png

可以看出下载的资源使用时间比CPU上下文切换的时间更多