最代码官方的gravatar头像
最代码官方 2017-05-16 00:19:13
spring事务中使用java synchronized线程同步字符串无效的案例说明

最近发现最代码会有重复的event生成,比如微信扫描关注+5牛币,下载源码等操作,很多牛牛之前反映过此类问题没有在意,最近有时间来深入研究下此类问题。

线上出错案例:

spring事务中使用java synchronized线程同步字符串无效的案例说明

spring事务中使用java synchronized线程同步字符串无效的案例说明

于是在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();
        }
    }

spring事务中使用java synchronized线程同步字符串无效的案例说明

之后将synchronized关键词加到方法上测试也可以实现并发加锁机制,真是百思不得其解呀。

后来想到应该不是java线程语法的问题,或许是和spring有关,于是百度"spring synchronized intern"得到一篇文章:

关于synchronized锁和Spring事务

才恍然大悟,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,除非某个线程中该方法的代码全部执行完毕提交了事务。

果断改写代码

spring事务中使用java synchronized线程同步字符串无效的案例说明

测试后log打印输出正常

spring事务中使用java synchronized线程同步字符串无效的案例说明

enjoy it all 牛牛!


打赏
最近浏览
kw11666  LV6 2022年9月22日
走丢的小怪兽  LV8 2022年3月2日
zhengchenghui  LV5 2021年7月30日
大熊321  LV1 2021年4月1日
thankspast 2020年4月11日
暂无贡献等级
3260813771 2019年12月30日
暂无贡献等级
luyuhan  LV1 2019年9月27日
weichi  LV1 2019年6月25日
一天一点爱恋  LV5 2019年4月7日
menglizuixin  LV1 2018年11月23日
顶部 客服 微信二维码 底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友