V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fronted
V2EX  ›  程序员

关于 synchronized 锁定对象出现的问题

  •  
  •   fronted · 2022-11-22 11:24:46 +08:00 · 2646 次点击
    这是一个创建于 730 天前的主题,其中的信息可能已经有所发展或是发生改变。

    代码如下,add 方法中有一个 synchronized 代码块,锁定的是 uid 字符串,代码中的逻辑也很简单,先查询没有则新增,有则不做操作。但是这个方法会出现重复新增的情况,也就是没锁住,多个线程进来了。发生这种情况的数据量也很少,大概几千条数据就一两条出现这种情况.大家有什么看法吗?

    补充一下:出现上述并发情况下打印过锁定对象的内存地址值,结果发现地址值也是一样的。

    public class StringPoolUtil {
        private static final Interner<String> POOL = Interners.newWeakInterner();
    
        public static Interner<String> getPool(){
            return POOL;
        }
    }
    
    public void add(String uid){
        synchronized (StringPoolUtil.getPool().intern(uid)) {
        // 查询数据
        // 没有数据则新增
        }
    }
    
    30 条回复    2022-11-23 18:14:06 +08:00
    GThui
        1
    GThui  
       2022-11-22 11:30:34 +08:00 via Android
    肯定锁对象不安全了。可能 uid 或者 StringPoolUtil 方法可能和 gc 也有关系
    fronted
        2
    fronted  
    OP
       2022-11-22 11:34:44 +08:00
    @GThui 打印过锁定对象的地址值,都是一样的
    dqzcwxb
        3
    dqzcwxb  
       2022-11-22 11:45:05 +08:00
    项目部署了几个
    fronted
        4
    fronted  
    OP
       2022-11-22 11:45:41 +08:00
    @dqzcwxb 单项目哈
    lovedoing
        5
    lovedoing  
       2022-11-22 11:58:27 +08:00
    Interners 里面的 String 也是会被 gc 的
    Hurriance
        6
    Hurriance  
       2022-11-22 12:18:15 +08:00
    「没有数据则新增」这里可以打个日志看看吗?可能是其他线程真的也没查到数据?
    codehz
        7
    codehz  
       2022-11-22 13:16:11 +08:00
    虽然不是很懂 interner ,但是稍微查看了下文档,这应该要用 newStrongInterner 才对吧。。。
    fronted
        8
    fronted  
    OP
       2022-11-22 14:38:56 +08:00
    @Hurriance 是其他线程没有查到数据,但是锁定的同一个对象,没释放锁其他线程不知道怎么的也进来了
    fronted
        9
    fronted  
    OP
       2022-11-22 16:04:41 +08:00
    @codehz 有道理,但是我这里出现的这个问题没有发生 gc 这种情况
    zilongzixue
        10
    zilongzixue  
       2022-11-22 16:08:33 +08:00
    直接上 redisson 分布式锁好了,一步到位
    sprit
        11
    sprit  
       2022-11-22 16:13:22 +08:00
    有无可能不是 SYNC 的问题,是 MySQL 的问题[dog]
    liudaolunhuibl
        12
    liudaolunhuibl  
       2022-11-22 16:24:35 +08:00
    看一下“StringPoolUtil.getPool().intern(uid)”这个呢?

    为什么不直接 uid.intern()呢?
    liudaolunhuibl
        13
    liudaolunhuibl  
       2022-11-22 17:01:02 +08:00
    jdk7 之后可以通过-XX:StringTableSize 参数来修改 StringTable 的大小,先试试改为用 String 的 intern 吧,你这个本来也是 String 对象
    liudaolunhuibl
        14
    liudaolunhuibl  
       2022-11-22 17:09:10 +08:00
    也可以换成 Interners.newStrongInterner()
    exqibao
        15
    exqibao  
       2022-11-22 17:10:45 +08:00
    会不会是 synchronized 块已经结束了,但事务还没有提交。
    corningsun
        16
    corningsun  
       2022-11-22 17:21:51 +08:00
    数据库加唯一索引吧?
    suStudent
        17
    suStudent  
       2022-11-22 17:37:23 +08:00
    弱引用:弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。

    怀疑多次加锁时,中间经历过一次垃圾回收。
    fronted
        18
    fronted  
    OP
       2022-11-22 17:42:21 +08:00
    @suStudent 应该不至于 我在进入 synchronized 打印过锁定的对象地址,值都是一样的
    night98
        19
    night98  
       2022-11-23 03:43:08 +08:00
    楼上说的都挺有道理,我补充一条吧,sync 应该包裹 add 方法而不是在 add 方法体内,否则会存在极小的时间差即 return 后事务注解执行 commit 方法前进入方法
    suStudent
        20
    suStudent  
       2022-11-23 09:18:25 +08:00
    @fronted 对应地址值? hashCode()? hashCode 可能相等。网上的例子"重地"和"通话" hashCode 值是一样的
    fronted
        21
    fronted  
    OP
       2022-11-23 09:45:45 +08:00
    @night98 主要是我想针对每个 uid 加锁,而不是调用该方法的用户都进行加锁
    fronted
        22
    fronted  
    OP
       2022-11-23 09:48:55 +08:00
    @suStudent 出现这种情况的时候锁对象的 hashcode 与值都是一样的
    fronted
        23
    fronted  
    OP
       2022-11-23 09:49:45 +08:00
    @exqibao 这种一般是怎么引起的呢,我这边暂时无法重现这种情况
    fronted
        24
    fronted  
    OP
       2022-11-23 09:50:46 +08:00
    @liudaolunhuibl 我先试试你的做法
    312ybj22
        25
    312ybj22  
       2022-11-23 11:19:49 +08:00
    在并发情况也要考虑下数据库的问题, 先看下数据库的隔离级别,在并发情况下事务还没提交,redo log 还没写到内存里,下个请求从数据库是读不到的,这样子就有并发问题了, 你可以改成编程式事务,手动提交后,再结束这把锁
    buster
        26
    buster  
       2022-11-23 11:45:23 +08:00
    看了你的思路,我觉得你可以考虑一下,构建一个 Object[] 长度=16 ,然后你的 hashcode 对 16 取模,决定获取第几个 object 来加 sync 锁,觉得能满足你的需求
    fronted
        27
    fronted  
    OP
       2022-11-23 11:51:20 +08:00
    @312ybj22 你这种确实是有道理的,不过我好奇的是,那不是会出现有并发的情况要进行编程式事务处理
    exqibao
        28
    exqibao  
       2022-11-23 12:19:09 +08:00
    大概就是假如 save 方法开启事务,add 方法就是带有 sync 的方法,save 方法里面调用 add 方法。
    这时 add 结束了,sync 锁已经释放了,但 save 方法还没结束,还没提交事物。
    这样 sync 释放到事务 commit 之间,假如来一样的数据,sync 锁过了,查数据库也没数据,数据就重复提交了。
    312ybj22
        29
    312ybj22  
       2022-11-23 17:02:33 +08:00
    @fronted 这问题就很有意思了, 用我组长的话来讲“这个一道很好的面试题”, 这样吧,你先试试用低级别的隔离级别,比如 RC ,这样并发度高些,看看会不会有重复的问题。 另外你查询的时候最好走索引,能用行锁用行锁。 先把问题解决,我后面也研究下这个问题,有结果我扣你
    Red998
        30
    Red998  
       2022-11-23 18:14:06 +08:00
    加个唯一索引 重复问题。按道理 一般的锁都是 object 你这个写法少见。锁也就是对象头的一个标识。建议不用 String
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1224 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 23:22 · PVG 07:22 · LAX 15:22 · JFK 18:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.