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

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

  •  
  •   fronted · 66 天前 · 1888 次点击
    这是一个创建于 66 天前的主题,其中的信息可能已经有所发展或是发生改变。

    代码如下,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  
       66 天前 via Android
    肯定锁对象不安全了。可能 uid 或者 StringPoolUtil 方法可能和 gc 也有关系
    fronted
        2
    fronted  
    OP
       66 天前
    @GThui 打印过锁定对象的地址值,都是一样的
    dqzcwxb
        3
    dqzcwxb  
       66 天前
    项目部署了几个
    fronted
        4
    fronted  
    OP
       66 天前
    @dqzcwxb 单项目哈
    lovedoing
        5
    lovedoing  
       66 天前
    Interners 里面的 String 也是会被 gc 的
    Hurriance
        6
    Hurriance  
       66 天前
    「没有数据则新增」这里可以打个日志看看吗?可能是其他线程真的也没查到数据?
    codehz
        7
    codehz  
       66 天前
    虽然不是很懂 interner ,但是稍微查看了下文档,这应该要用 newStrongInterner 才对吧。。。
    fronted
        8
    fronted  
    OP
       66 天前
    @Hurriance 是其他线程没有查到数据,但是锁定的同一个对象,没释放锁其他线程不知道怎么的也进来了
    fronted
        9
    fronted  
    OP
       66 天前
    @codehz 有道理,但是我这里出现的这个问题没有发生 gc 这种情况
    zilongzixue
        10
    zilongzixue  
       66 天前
    直接上 redisson 分布式锁好了,一步到位
    sprit
        11
    sprit  
       66 天前
    有无可能不是 SYNC 的问题,是 MySQL 的问题[dog]
    liudaolunhuibl
        12
    liudaolunhuibl  
       66 天前
    看一下“StringPoolUtil.getPool().intern(uid)”这个呢?

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

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