“NoProvider”的版本间的差异
		
		
		
		
		
		跳到导航
		跳到搜索
		
			
		
		
	
| Jihongchang(讨论 | 贡献)   (建立内容为“NoProvider.java<syntaxhighlight lang="java"> import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestCompo…”的新页面) | 
| (没有差异) | 
2025年8月28日 (四) 09:19的最新版本
NoProvider.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.TestComponent;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@TestComponent
public class NoProvider {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    private final Queue<String> noQueue = new ConcurrentLinkedQueue<>();
    // 线程安全锁,仅在填充队列时使用
    private final Lock lock = new ReentrantLock();
    // Spring Bean 的默认作用域是单例,所以单例 Bean 的属性会被所有线程共享
    private int lastMaxNoInt = -1; //记录上次查询到的最大数值,用于分页查询
    private static final int TARGET_SIZE = 1000;
    // 分页查询时使用的页容量
    private static final int PAGE_SIZE = 1000;
    private static final String SQL = "select * from student where no > ? order by no asc limit ?";
    public String poll() {
        // 在大部分时候对 poll 的调用时,noQueue 里都不是空的,所以把不为空的、不用上锁的逻辑写在前面
        // 无锁尝试获取元素
        String no = noQueue.poll();
        if (no != null) {
            return no;
        }
        /*
        如果同时多个线程走到这里,除第一个线程上锁成功外,其他线程都将等待;
        核心原理:Lock 的内存语义(对应JMM规范)
        Java 中的 Lock接口(如ReentrantLock的实现)严格遵循 JMM 的内存语义,
        其作用等价于 synchronized 的“加锁-解锁”,但更灵活。具体到这个场景:
        (1)第一个线程“解锁(unlock)”时的动作
            当第1个线程执行 lock.unlock() 时,
            JMM 会强制将该线程工作内存中修改后的 lastMaxNoInt 值刷新到主内存(相当于“写回”主内存)。
             注:线程不会直接操作主内存,而是先将主内存的数据加载到自己的“工作内存”(CPU 缓存、寄存器等)中修改,
             解锁时必须把修改结果同步回主内存,避免数据只存在于当前线程的工作内存中。
        (2)下一个线程“加锁(lock)”时的动作
            当下一个线程执行lock.lock()并成功获取锁时,
            JMM会强制该线程清空自己的工作内存中 lastMaxNoInt 的旧值,
            然后从主内存重新加载 lastMaxNoInt 的最新值(也就是第一个线程刚刷回主内存的新值)到自己的工作内存中。
                这一步保证了下一个线程进入临界区后,拿到的 lastMaxNoInt 一定是最新的,
                而不是自己工作内存中缓存的旧值。
        */
        // 队列空,加锁填充
        lock.lock();
        /*
        在操作锁(或其他需要手动释放的资源,如文件流、数据库连接)时,
        try-finally不是“可选写法”,而是必须的安全保障:
            直接在 return 前 unlock 的写法,无法处理“代码执行过程中抛出异常”的场景,会导致锁泄露;
            try-finally 能确保锁在任何情况下都被释放,这在多线程共享的 Spring Bean 中至关重要(一旦锁泄露,整个服务可能瘫痪)。
        这也是 Java 并发编程中操作锁的 标准写法,目的是保证锁的可靠性和程序的健壮性。
        */
        try {
            // 双重检查:防止多线程同时进入填充逻辑
            no = noQueue.poll();
            if (no != null) {
                return no;
            }
            fillQueueToTargetSize();
            return noQueue.poll();
        } finally {
            lock.unlock();
        }
    }
    private void fillQueueToTargetSize() {
        while (noQueue.size() < TARGET_SIZE) {
            String noStart = String.format("%08d", lastMaxNoInt);
            List<String> noList = jdbcTemplate.queryForList(SQL, new Object[]{lastMaxNoInt, PAGE_SIZE}, String.class);
            if (noList.isEmpty()) {
                fillQueueToTargetSizeWhenNoListIsEmpty();
                break;
            }
            // 每一个 no 肯定都比 lastMaxNoInt 大
            for (int i = 0; noQueue.size() < TARGET_SIZE && i < noList.size(); i++) {
                String no = noList.get(i);
                int noInt = Integer.parseInt(no);
                while (noQueue.size() < TARGET_SIZE && ++lastMaxNoInt < noInt) {
                    String newNo = String.format("%08d", lastMaxNoInt);
                    noQueue.offer(newNo);
                }
            }
            System.out.printf("lastMaxNoInt=%d\n", lastMaxNoInt);
        } // end while (noQueue.size() < TARGET_SIZE) {
    }
    private void fillQueueToTargetSizeWhenNoListIsEmpty() {
        // 没有更多数据,使用后续自增数字补充
        int remaining = TARGET_SIZE - noQueue.size();
        for (int i = 0; i < remaining; i++) {
            lastMaxNoInt++;
            String newNo = String.format("%08d", lastMaxNoInt);
            noQueue.offer(newNo);
        }
        System.out.printf("lastMaxNoInt=%d\n", lastMaxNoInt);
    }
}