cantroller 层
@PostMapping("/traffic/records")
public List
}
@GetMapping("/visitor/record")
public List
public List<DoorRecordInfoVO> doorRecordList(String typeFlag) {
reentrantLock.lock();
try {
//首先获取 token
String token = this.getToken();
if (StrUtil.isEmpty(token)) {
return null;
}
List<DoorRecordInfoVO> doorRecordInfoVOList = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
LocalDateTime start = now.minusHours(12L);
return handleList(doorRecordInfoVOList, now, start, token, typeFlag, 0);
}finally {
reentrantLock.unlock();
}
}
private List<DoorRecordInfoVO> handleList(List<DoorRecordInfoVO> doorRecordInfoVOList,
LocalDateTime now,
LocalDateTime start,
String token,
String typeFlag,
int retryCount) {
//判断两个时间是不是在同一天
if (now.toLocalDate().equals(start.toLocalDate())) {
//在同一天组装时间范围
String date = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String startTime = start.format(DateTimeFormatter.ofPattern("HHmmss"));
String endTime = now.format(DateTimeFormatter.ofPattern("HHmmss"));
JSONArray recordArray = this.apiResult(token, date, startTime, endTime);
this.addArrayToList(recordArray, doorRecordInfoVOList, typeFlag);
} else {
//不在同一天
String date = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String endTime = now.format(DateTimeFormatter.ofPattern("HHmmss"));
JSONArray recordArray1 = this.apiResult(token, date, "000000", endTime);
this.addArrayToList(recordArray1, doorRecordInfoVOList, typeFlag);
//加上前一天时间段
String preDate = start.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String startTime = start.format(DateTimeFormatter.ofPattern("HHmmss"));
JSONArray recordArray2 = this.apiResult(token, preDate, startTime, "235959");
this.addArrayToList(recordArray2, doorRecordInfoVOList, typeFlag);
}
if (doorRecordInfoVOList.size() < 3) {
now = start;
start = start.minusHours(1L);
handleList(doorRecordInfoVOList, now, start, token, typeFlag, retryCount + 1);
}
return doorRecordInfoVOList;
}
private void addArrayToList(JSONArray array, List<DoorRecordInfoVO> doorRecordInfoVOList, String typeFlag) {
if (ObjectUtil.isEmpty(array)) return;
for (int i = 0; i < array.size(); i++) {
JSONObject record = array.getJSONObject(i);
DoorRecordInfoVO doorRecordInfoVO = new DoorRecordInfoVO();
String type = record.getStr("recogResult");
doorRecordInfoVO.setUserName(record.getStr("userName"));
doorRecordInfoVO.setDoorName(record.getStr("doorName"));
doorRecordInfoVO.setOpenTime(
HcDateUtil.dateFormat(record.getStr("openTime"), "yyyyMMddHHmmss", "yyyy-MM-dd HH:mm:ss")
);
doorRecordInfoVO.setRecordType(type);
if (StrUtil.isNotEmpty(typeFlag) && typeFlag.equals(type)) {
doorRecordInfoVOList.add(doorRecordInfoVO);
} else if (StrUtil.isEmpty(typeFlag)) {
doorRecordInfoVOList.add(doorRecordInfoVO);
}
}
}
private JSONArray apiResult(String token, String date, String startTime, String endTime) {
JSONArray jsonArray = null;
//组装请求参数
Map<String, Object> requestMap = new HashMap<>();
Map<String, Object> map = new HashMap<>();
map.put("beginTime", startTime);
map.put("endTime", endTime);
map.put("date", date);
requestMap.put("data", map);
//调用 api 获取数据
String body = HttpRequest
.post(getDoorRecordInfoUrl)
.header("token", token)
.header("appId", appId)
.header("Content-Type", "application/json")
.body(JSONUtil.toJsonStr(requestMap))
.execute()
.body();
JSONObject resultJson = JSONUtil.parseObj(body);
Integer code = resultJson.getInt("code");
if (code == 200) {
JSONObject data = resultJson.getJSONObject("data");
jsonArray = data.getJSONArray("data");
}
return jsonArray;
}
上述实现如果我不加上 reentrantLock ,doorRecordInfoVOList 线程安全问题,有一个接口直接返回空了,但是直接分开调用,都有返回,请问这个为啥会有线程安全问题,求大佬指教
1
chendy 312 天前
代码好乱
盲猜问题出在 this.getToken() |
2
totoro52 312 天前
提供的代码中使用了 ReentrantLock 来同步对关键部分的访问。在 doorRecordList 方法内部对 handleList 方法的递归调用中存在潜在问题。在 doorRecordList 方法开始时获取了锁,并在 finally 块中释放了锁。如果递归调用 handleList 发生,可能会导致再次获取锁而不释放,导致死锁。
|
3
mango88 312 天前
检查下你的 this.getToken() 方法把
|
4
Bryant0814 OP 上述漏了一个 handleList 这个方法中最后那个判断是 doorRecordInfoVOList.size() < 3 && retryCount < 5 ,我的问题是,不加锁之前的话,调用这两个接口只有一个返回了数据,应该就是 doorRecordInfoVOList 出现了线程共享问题
|
5
Bryant0814 OP |
6
fordes 312 天前
后端出现线程问题可能是因为在 doorRecordList()服务方法中使用了全局的 ReentrantLock 对象( reentrantLock ),而在 Controller 层中有两个接口同时调用此服务方法。
当一个线程进入 doorRecordList()方法并获取锁之后,如果此时另一个线程也试图通过另一个接口调用该方法获取锁,由于锁已经被占用,第二个线程将会被阻塞,直到第一个线程执行完方法体内的逻辑并释放锁。在高并发场景下,如果前端页面异步同时调用这两个接口,可能会导致线程争抢锁资源,从而影响系统性能和响应速度。 另外,handleList() 方法内部递归调用自身时,每次递归都会尝试获取锁,这也会增加线程同步的复杂性。特别是在递归过程中涉及到网络 IO 操作(如 apiResult()方法中的 HTTP 请求),可能导致线程长时间持有锁而不释放,进而影响其他线程的执行。 为了解决这个问题,可以考虑以下策略: 确认是否需要在整个方法上加锁,根据业务需求看是否能将锁的作用范围缩小至实际有共享数据竞争的部分。 如果确实需要全局锁来保证数据一致性,可以考虑使用信号量或者条件变量等更细粒度的同步机制,以减少线程间的相互阻塞。 对于 HTTP 请求这类耗时操作,尽量避免在同步代码块或持有锁的情况下进行,可以通过异步处理、多线程池等方式提高并发能力。 总之,在设计并发访问的方法时,应确保正确合理地管理锁,避免造成不必要的线程阻塞,提高系统的并发处理能力和响应速度。 (通义千问的回答) |
7
Bryant0814 OP @fordes 我的问题是为什么,不加上 reentrantLock ,doorRecordInfoVOList 线程安全问题
|
8
vvtf 312 天前
|
9
Bryant0814 OP @totoro52 问题是不加上 reentrantLock ,doorRecordInfoVOList 线程安全问题
|
10
chendy 312 天前 1
@Bryant0814 emm…按照我能看懂的部分,你接口返空唯一的点是
String token = this.getToken(); if (StrUtil.isEmpty(token)) { return null; } 所以建议检查一下这里面有没有啥线程不安全的操作 顺便一说,线程不安全的前提之一是有被并发操作的成员变量,从你的代码上看不出来有这种情况 顺便 @Livid 楼上有个 AI 搬运工 |
11
sparklee 312 天前
返回空 是因为 getToken() empty 吗? doorRecordInfoVOList 没有多个线程共享吧, 应该不是这个问题
|
12
lsk569937453 312 天前
1.先把锁去了,这不是排查问题的方法。
2.handleList 里面我没看错的话是无限重试。在你的能力达不到的时候不要用递归。 3.后端能不重试就不重试,如果一个 http 请求出错了,你马上发一个请求,大概率也是出错的。 4.然后打日志看下 String token = this.getToken(); 这个返回值 |
13
Bryant0814 OP 已解决,是 token 问题,感谢各位大佬!!!
|