最近发现最代码会有重复的event生成,比如微信扫描关注+5牛币,下载源码等操作,很多牛牛之前反映过此类问题没有在意,最近有时间来深入研究下此类问题。
线上出错案例:
于是在Service层的方法做了如下同步操作:
private Event checkProjectEvent(User user, Project project, int type) {
Event event = null;
Page<Event> events = findAllByTypeAndUserIdAndSourceId(
type, user.getId(), project.getId(),
1,
1);
if (events.getTotalElements() == 0) {
event = initEvent(user, type);
event.setSourceId(project.getId());
event.setSource(project);
event.setSourceUserId(project.getUserId());
event.setSourceUser(project.getUser());
System.out.println(currentThread().getName() + " new event " + user.getId() + "_" + project.getId() + "_" + type);
} else {
event = events.getContent().get(0);
System.out.println(currentThread().getName() + " get event " + user.getId() + "_" + project.getId() + "_" + type);
}
return event;
}
//同步检测同一用户,同一个project,同一type的event并发的情况
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void synchronizedCollectCreateProjectEvent(User user, Project project) {
int type = ModuleConstants.EVENT_TYPE_RULE_PROJECT_COLLECT;
String lock = getUserSourceTypeLock(user.getId(), project.getId(), type);
System.out.println(currentThread().getName() + " " + lock);
synchronized (lock.intern()) {
Event event = checkProjectEvent(user, project, type);
if (event.getId() != null && event.getStatus() >= ModuleConstants.MODULE_STATUS_NORMAL) {//已收藏则直接返回该event即可,无须再更新收藏数
return;
}
int status = ModuleConstants.MODULE_STATUS_NORMAL;
if (ModuleConstants.PUBLIC_EVENTS.contains(type)) {
status = ModuleConstants.EVENT_STATUS_PUBLIC;
}
event.setStatus(status);
save(event);
}
JSONObject extendJson = project.getExtend();
int collectCount = extendJson
.getInt(ModuleConstants.PROJECT_EXTEND_JSON_COLLECT_COUNT);
collectCount++;
extendJson.put(ModuleConstants.PROJECT_EXTEND_JSON_COLLECT_COUNT,
collectCount);
project.setExtendJson(extendJson.toString());
project.setExtend(extendJson);
projectService.save(project);
}
但是发现连续点击收藏按钮并发时还是出现多次new event的log
http-apr-80-exec-3 1_6183_304
http-apr-80-exec-3 new event 1_6183_304
http-apr-80-exec-5 1_6183_304
http-apr-80-exec-5 new event 1_6183_304
http-apr-80-exec-4 get event 1_6183_303
http-apr-80-exec-7 1_6183_304
http-apr-80-exec-7 get event 1_6183_304
http-apr-80-exec-9 get event 1_6183_303
http-apr-80-exec-6 get event 1_6183_303
通过以下单元测试方法确认java线程并发同步时加锁的机制是确认正常生效的
private static final HashMap EVENTMAP = new HashMap();
public static String synchronizedCollectRemoveProjectEvent(String userId, String projectId, String type, Thread thread) {
String lock = userId + "_" + projectId + "_" + type;
String id = null;
synchronized (lock.intern()) {
if (EVENTMAP.containsKey(lock)) {
id = EVENTMAP.remove(lock);
System.out.println(thread.getName() + " remove " + id);
} else {
System.out.println(thread.getName() + " not find " + id);
}
}
return id;
}
public static void main(String[] args) throws InterruptedException {
final String userId = "1";
final String projectId = "1";
final String type = "1";
for (int i = 0; i < 20; i++) {
new Thread("create" + i) {
public void run() {
Thread currentThread = currentThread();
String id = synchronizedCollectCreateProjectEvent(userId, projectId, type, currentThread);
}
}.start();
}
}
之后将synchronized关键词加到方法上测试也可以实现并发加锁机制,真是百思不得其解呀。
后来想到应该不是java线程语法的问题,或许是和spring有关,于是百度"spring synchronized intern"得到一篇文章:
才恍然大悟,synchronized同步肯定有效果,但是因为有spring事务配置,同步代码如下:
synchronized (lock.intern()) {
Event event = checkProjectEvent(user, project, type);
if (event.getId() != null && event.getStatus() >= ModuleConstants.MODULE_STATUS_NORMAL) {//已收藏则直接返回该event即可,无须再更新收藏数
return;
}
int status = ModuleConstants.MODULE_STATUS_NORMAL;
if (ModuleConstants.PUBLIC_EVENTS.contains(type)) {
status = ModuleConstants.EVENT_STATUS_PUBLIC;
}
event.setStatus(status);
save(event);
}
多个线程执行该block时,虽然调用了save event,但是因为该方法尚未执行结束,事务也尚未提交,所以多线程并发执行时,肯定没save event,每次返回的都是new event,除非某个线程中该方法的代码全部执行完毕提交了事务。
果断改写代码
测试后log打印输出正常
enjoy it all 牛牛!
最近浏览




