ConcurrentHashMap 的使用问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gramyang
V2EX    Java

ConcurrentHashMap 的使用问题

  •  
  •   gramyang 2019-06-12 07:41:42 +08:00 4713 次点击
    这是一个创建于 2383 天前的主题,其中的信息可能已经有所发展或是发生改变。

    昨天在 netty 的 handler 里碰到了一个非常奇怪的问题: 1、首先,handler 没有加 sharable 注解 2、我在 handler 的外部生成了一个 concurrenthashmap 实例并传入 handler 3、在 handler 的一个方法中调用 concurrenthashmap 的 remove(player.getNum()),然后再调用 player=null 将 player 清空。

    这个时候奇迹出现了,remove 报指针,也就是 remove 的时候 player 是 null。 我检查完了所有的代码,再没有其他地方把 player 设置为 null,并且 remove 的操作前还判断了 player!=null。

    思来想去,只有两个可能: Java 中的指令重排序,导致 player 在 remove 之前就被空置,但是感觉不太可能啊。。。 concurrenthashmap 是多个线程共享的变量,直接 remove 会出现并发问题。。。

    请大神指导!!

    第 1 条附言    2019-06-12 08:42:35 +08:00
    private void handleAfterExitOrException() {
    if(player != null && player.getSeatNum() > -1) {
    apiHandler.exitOrException();
    }
    if (player != null && player.getUserName() != null) {
    userName2Player.remove(player.getUserName());
    log.info("{}退出系统了", player.getUserName());
    }
    player = null;
    }

    public void exitOrException() {
    playerMap.remove(player.getSeatNum());
    Table thisTable = tableMap.get(player.getTableNum());
    thisTable.getPlayers().remove(player);
    int count = thisTable.getPlayCount();
    thisTable.setPlayCount(count - 1);
    thisTable.setPlay(false);
    thisTable.setRob(false);
    thisTable.setWait(true);
    //通知游戏房间里其他玩家
    ExitSeatResponse respOnse= new ExitSeatResponse(player.getUserName(), player.getSeatNum(), refreshSeatNum2UserName(thisTable));
    batchSendMsg(response.getClass().getSimpleName() + JSON.toJSONString(response),
    thisTable.getPlayers(), true);
    //通知游戏大厅里所有玩家有人退出房间
    for(HallTable hallTable : hallList) {
    if(hallTable.getTableNum() == thisTable.getTableNum()) {
    hallTable.setFull(false);
    hallTable.setPlay(false);
    hallTable.getUserNames().remove(player.getUserName());
    }
    }
    RefreshHallResponse response1 = new RefreshHallResponse(hallList);
    batchSendMsg(response1.getClass().getSimpleName() + JSON.toJSONString(response1),
    userName2Player.values(), true);
    userName2Player.remove(player.getUserName());
    }
    29 条回复    2019-06-13 16:11:12 +08:00
    nazor
        1
    nazor  
       2019-06-12 07:44:09 +08:00 via iPhone
    remove 空指针,是因为 hashmap 为 null
    temp178
        2
    temp178  
       2019-06-12 07:45:39 +08:00
    1. player 是如何生成的?是否会有多个 handler 去 remove 同一个 player 的情况,这种情况可能会导致 null 异常
    temp178
        3
    temp178  
       2019-06-12 07:52:20 +08:00
    2.看了#1 的回复,楼主确定下到底是因为什么为 null 导致的 null 异常?或者 debug 一下?
    nazor
        4
    nazor  
       2019-06-12 07:54:05 +08:00 via iPhone
    确定不是 geuNum 返回 null? player 为 null,执行不到 remove 吧
    gramyang
        5
    gramyang  
    OP
       2019-06-12 07:56:55 +08:00
    @nazor 不会吧,不可能啊,remove 出空指针不是 remove 传入的变量为 null 吗?
    gramyang
        6
    gramyang  
    OP
       2019-06-12 07:58:53 +08:00
    @mejee player 是 handler 的私有变量,concurrenthashmap 是 handler 外部传入的变量。会有 concurrenthashmap 同时 remove 多个 player 的情况
    gramyang
        7
    gramyang  
    OP
       2019-06-12 07:59:50 +08:00
    @nazor 不会,因为前面代码有 player!=null 和 player.getNum>1 的判断
    luckylo
        8
    luckylo  
       2019-06-12 08:03:20 +08:00 via Android
    @gramyang 应该是 map 本身为空。remove 会返回 remove 的值,如果没有对应的 key,应该不会报空指针,最多应该就是返回 null。
    gramyang
        9
    gramyang  
    OP
       2019-06-12 08:06:29 +08:00
    @luckylo 代码层面上,map 不可能为空,我是初始化之后才传进去的。另外 concurrenthashmap 的 remove 方法源码上的注释:
    @throws NullPointerException if the specified key is null
    luckylo
        10
    luckylo  
       2019-06-12 08:08:37 +08:00 via Android
    luckylo
        11
    luckylo  
       2019-06-12 08:09:11 +08:00 via Android
    @gramyang 我刚去翻文档了
    temp178
        12
    temp178  
       2019-06-12 08:16:58 +08:00
    @luckylo
    @gramyang 刚去验证了下,
    @luckylo 说的对,没有对应 key 不会报 null 异常,是我记错了
    YzSama
        13
    YzSama  
       2019-06-12 08:32:49 +08:00 via iPhone
    show me the code。XD
    gramyang
        14
    gramyang  
    OP
       2019-06-12 08:42:14 +08:00
    @YzSama 贴在问题后面了,但是代码很多很杂,还是文字描述更精炼一些
    xuanbg
        15
    xuanbg  
       2019-06-12 09:11:02 +08:00
    好多个 remove,到底是哪一行抛了空指针?
    anzu
        16
    anzu  
       2019-06-12 09:41:03 +08:00
    handleAfterExitOrException 没有锁,当并发执行的时候,player 随时会被其它线程置 null,检查是否为 null 没用。
    passerbytiny
        17
    passerbytiny  
       2019-06-12 10:00:16 +08:00
    不太确定没有 sharable 注解的时候,handler 就是单个连接通道独占的。问题可能出在这里。
    Macolor21
        18
    Macolor21  
       2019-06-12 10:01:40 +08:00
    代码是 playerMap.remove( player.getSeatNum() );
    这里抛出空指针异常,要不就是 map 空,要不就是 player 空,标题起的有歧义,应该是执行 apiHandler.exitOrException();时,player 被其他线程置 null
    passerbytiny
        19
    passerbytiny  
       2019-06-12 10:05:55 +08:00
    这里建议用 ChannelContext 或者 Channel 的属性去保存 player,它们确定是线程安全或者单个通道独享的。
    cookii
        20
    cookii  
       2019-06-12 10:07:15 +08:00
    楼主说了,Handler 没有 sharable,所以 Handler 不会并发被调用,一个 handler 总是在同一个线程中被执行。所以在同一个线程中,就不存在重排序的问题。这个问题看起来比较诡异,建议打断点观看变量的值。
    gramyang
        21
    gramyang  
    OP
       2019-06-12 10:19:33 +08:00
    @xuanbg exitOrException 的第一个 remove
    gramyang
        22
    gramyang  
    OP
       2019-06-12 10:21:25 +08:00
    @imzhoukunqiang 是的,很诡异。说实话,上面的代码已经是我修改过了的,不过意思没变,都是很诡异的空指针。
    passerbytiny
        23
    passerbytiny  
       2019-06-12 10:25:51 +08:00
    去翻了一下 https://netty.io/4.0/api/io/netty/channel/ChannelHandler.Sharable.html,没有 Sharable 的时候,Handle 是单个通道独占的。

    到目前为止,根据楼主已放出来的消息,找不出其他原因了。
    gramyang
        24
    gramyang  
    OP
       2019-06-12 10:31:59 +08:00
    @passerbytiny 也足够了,起码帮助排除了重排序和并发错误的可能性。修改代码后如果再出现这种错误再另说
    firefffffffffly
        25
    firefffffffffly  
       2019-06-12 10:41:50 +08:00
    建议把 exception 信息贴出来,这样能轻松确定是 map 为空还是传入的 key 值为空。
    从描述的 exception 来看 player 最不可能为空,因为这样的话报错 message 和 traces 里是不会包含 remove 相关内容的,因为在 player.getNum()时就会报错了,remove 函数还没有入栈。
    key 值为空的情况,就是 player.getNum()的结果为 null,这个 player 内部属性需要再检查一下是否有多线程修改。
    rainmakeroly
        26
    rainmakeroly  
       2019-06-12 10:44:57 +08:00 via Android
    player 的获取,设置,初始化。报错信息的话主要是它吧
    alamaya
        27
    alamaya  
       2019-06-12 11:48:40 +08:00
    你这个 apiHandler 是怎么来的?没看出来你的 player 是怎么传入的
    senninha
        28
    senninha  
       2019-06-12 16:34:55 +08:00
    - -player 在其他线程并发置 null 了?有其他线程在操作这个 player ?如果其他线程要操作,可以丢到 eventloop 里转成同步执行,保证并发安全。
    ps:直接在 handler 里写业务代码的吗?这么强悍。。
    laodao1990
        29
    laodao1990  
       2019-06-13 16:11:12 +08:00
    要不这样试试:
    if player!=null
    锁{
    if player!=null {
    remove
    }
    }
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     820 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 21:26 PVG 05:26 LAX 13:26 JFK 16:26
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86