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

ConcurrentHashMap 的使用问题

  •  
  •   gramyang · 13 天前 · 1778 次点击

    昨天在 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 条附言  ·  13 天前
    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
        1
    nazor   13 天前 via iPhone
    remove 空指针,是因为 hashmap 为 null
        2
    mejee   13 天前
    1. player 是如何生成的?是否会有多个 handler 去 remove 同一个 player 的情况,这种情况可能会导致 null 异常
        3
    mejee   13 天前
    2.看了#1 的回复,楼主确定下到底是因为什么为 null 导致的 null 异常?或者 debug 一下?
        4
    nazor   13 天前 via iPhone
    确定不是 geuNum 返回 null? player 为 null,执行不到 remove 吧
        5
    gramyang   13 天前
    @nazor 不会吧,不可能啊,remove 出空指针不是 remove 传入的变量为 null 吗?
        6
    gramyang   13 天前
    @mejee player 是 handler 的私有变量,concurrenthashmap 是 handler 外部传入的变量。会有 concurrenthashmap 同时 remove 多个 player 的情况
        7
    gramyang   13 天前
    @nazor 不会,因为前面代码有 player!=null 和 player.getNum>1 的判断
        8
    luckylo   13 天前 via Android
    @gramyang 应该是 map 本身为空。remove 会返回 remove 的值,如果没有对应的 key,应该不会报空指针,最多应该就是返回 null。
        9
    gramyang   13 天前
    @luckylo 代码层面上,map 不可能为空,我是初始化之后才传进去的。另外 concurrenthashmap 的 remove 方法源码上的注释:
    @throws NullPointerException if the specified key is null
        10
    luckylo   13 天前 via Android
        11
    luckylo   13 天前 via Android
    @gramyang 我刚去翻文档了😂
        12
    mejee   13 天前
    @luckylo
    @gramyang 刚去验证了下,
    @luckylo 说的对,没有对应 key 不会报 null 异常,是我记错了
        13
    YzSama   13 天前 via iPhone
    show me the code。XD
        14
    gramyang   13 天前
    @YzSama 贴在问题后面了,但是代码很多很杂,还是文字描述更精炼一些
        15
    xuanbg   13 天前
    好多个 remove,到底是哪一行抛了空指针?
        16
    anzu   13 天前
    handleAfterExitOrException 没有锁,当并发执行的时候,player 随时会被其它线程置 null,检查是否为 null 没用。
        17
    passerbytiny   13 天前
    不太确定没有 sharable 注解的时候,handler 就是单个连接通道独占的。问题可能出在这里。
        18
    Macolor21   13 天前
    代码是 playerMap.remove( player.getSeatNum() );
    这里抛出空指针异常,要不就是 map 空,要不就是 player 空,标题起的有歧义,应该是执行 apiHandler.exitOrException();时,player 被其他线程置 null
        19
    passerbytiny   13 天前
    这里建议用 ChannelContext 或者 Channel 的属性去保存 player,它们确定是线程安全或者单个通道独享的。
        20
    imzhoukunqiang   13 天前
    楼主说了,Handler 没有 sharable,所以 Handler 不会并发被调用,一个 handler 总是在同一个线程中被执行。所以在同一个线程中,就不存在重排序的问题。这个问题看起来比较诡异,建议打断点观看变量的值。
        21
    gramyang   13 天前
    @xuanbg exitOrException 的第一个 remove
        22
    gramyang   13 天前
    @imzhoukunqiang 是的,很诡异。说实话,上面的代码已经是我修改过了的,不过意思没变,都是很诡异的空指针。
        23
    passerbytiny   13 天前
    去翻了一下 https://netty.io/4.0/api/io/netty/channel/ChannelHandler.Sharable.html,没有 Sharable 的时候,Handle 是单个通道独占的。

    到目前为止,根据楼主已放出来的消息,找不出其他原因了。
        24
    gramyang   13 天前
    @passerbytiny 也足够了,起码帮助排除了重排序和并发错误的可能性。修改代码后如果再出现这种错误再另说
        25
    firefffffffffly   13 天前
    建议把 exception 信息贴出来,这样能轻松确定是 map 为空还是传入的 key 值为空。
    从描述的 exception 来看 player 最不可能为空,因为这样的话报错 message 和 traces 里是不会包含 remove 相关内容的,因为在 player.getNum()时就会报错了,remove 函数还没有入栈。
    key 值为空的情况,就是 player.getNum()的结果为 null,这个 player 内部属性需要再检查一下是否有多线程修改。
        26
    rainmakeroly   13 天前 via Android
    player 的获取,设置,初始化。报错信息的话主要是它吧
        27
    alamaya   13 天前
    你这个 apiHandler 是怎么来的?没看出来你的 player 是怎么传入的
        28
    senninha   13 天前
    - -player 在其他线程并发置 null 了?有其他线程在操作这个 player ?如果其他线程要操作,可以丢到 eventloop 里转成同步执行,保证并发安全。
    ps:直接在 handler 里写业务代码的吗?这么强悍。。
        29
    laodao1990   12 天前
    要不这样试试:
    if player!=null
    锁{
    if player!=null {
    remove
    }
    }
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   910 人在线   最高记录 5043   ·   Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 20ms · UTC 22:06 · PVG 06:06 · LAX 15:06 · JFK 18:06
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1