查看“NoProvider”的源代码
←
NoProvider
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您所请求的操作仅限于该用户组的用户使用:
用户
您可以查看和复制此页面的源代码。
NoProvider.java<syntaxhighlight lang="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); } } </syntaxhighlight>
返回至
NoProvider
。
导航菜单
个人工具
登录
名字空间
页面
讨论
变种
视图
阅读
查看源代码
查看历史
更多
搜索
导航
首页
Spring Boot 2 零基础入门
Spring Cloud
Spring Boot
设计模式之禅
VUE
Vuex
Maven
算法
技能树
Wireshark
IntelliJ IDEA
ElasticSearch
VirtualBox
软考
正则表达式
程序员精讲
软件设计师精讲
初级程序员 历年真题
C
SQL
Java
FFmpeg
Redis
Kafka
MySQL
Spring
Docker
JMeter
Apache
Linux
Windows
Git
ZooKeeper
设计模式
Python
MyBatis
软件
数学
PHP
IntelliJ IDEA
CS基础知识
网络
项目
未分类
MediaWiki
镜像
问题
健身
国债
英语
烹饪
常见术语
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息