【问题标题】:Duplicated keys in Tree Map when accesing concurrently并发访问时Treemap中的重复键
【发布时间】:2016-12-07 15:07:12
【问题描述】:

有人能在这段代码中找到并发错误吗?该代码在一个线程上运行得非常好,但是当我同时启动 2 个线程并调用 addScore 方法时,它正在向树 Map 添加重复项。

compareTO 被覆盖的 pojo 如下:

public final class UserHighScore implements Comparable<UserHighScore>{

    private final int userId;
    private final int value;

    public UserHighScore(int userId, int value) {
        this.userId = userId;
        this.value = value;
    }

    public int getUserId() {
        return userId;
    }

    public int getValue() {
        return value;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof UserHighScore)) {
            return false;
        }
        UserHighScore userHighScore = (UserHighScore) obj;
        return userHighScore.userId==userId;
    }

    @Override
    public int compareTo(UserHighScore uh) {
        if(uh.getUserId()==this.getUserId()) return 0;
        if(uh.getValue()>this.getValue()) return 1;
        return -1;
    }
}

这是我用来模拟用户发出请求的代码:

class User implements Runnable
{

    private ScoreServiceImpl scoreService=ScoreServiceImpl.getInstance();

    CountDownLatch latch;
    public User(CountDownLatch latch)
    {
        this.latch = latch;
    }

    @Override
    public void run() {


        for(int i=0;i<5;i++) {
            scoreService.addScore(3,Integer.parseInt(Thread.currentThread().getName()),ThreadLocalRandom.current().nextInt(50000));
        }
        System.out.println(scoreService.getHighScoreList(3));
    }
}

创建线程的主要方法是:

public static void main(String[] args) throws InterruptedException {

    SpringApplication.run(RestclientApplication.class, args);

    CountDownLatch latch = new CountDownLatch(1);
    User user1=new User(latch);
    User user2=new User(latch);
    Thread t1=new Thread(user1);
    Thread t2=new Thread(user2);
    t1.setName("1");
    t2.setName("2");
    t1.start();
    t2.start();
    //latch.countDown();
}

【问题讨论】:

  • 你怎么知道地图中有重复的内容?
  • 因为当我调试时,我看到地图对于同一个 userId 有多个键,而当我打印列表时,我也可以看到
  • 对我来说,userId 的条目不应该超过一个条目(分数无关紧要),但是对于一个线程来说这很好,但对于两个线程来说却不行

标签: java multithreading concurrency treemap sortedmap


【解决方案1】:

您的 compareTo 很糟糕。你可以用这样的东西单线程获得相同的结果

    ScoreServiceImpl.getInstance().addScore(0,1,4);
    ScoreServiceImpl.getInstance().addScore(0,1,12);
    ScoreServiceImpl.getInstance().addScore(0,0,10);
    ScoreServiceImpl.getInstance().addScore(0,0,3);

树组通过分而治之的方式工作,它首先检查中间的人。 (这将是 1、4)并且由于 userIds 不匹配它不会比较它们而是比较值。如果它比较了用户 ID,它会向左走,但它会向右走,只比较用户 ID 为 1 的项目

您可以始终比较两个值,也可以始终仅比较 userId,但不能来回切换。

@Override
public int compareTo(UserHighScore uh) {
    return Integer.compare(userId, uh.userId);
}

【讨论】:

  • 所以根据您的回答,不可能使用自定义对象作为树形图中的键,同时具有键的唯一性和顺序,对吗?跨度>
  • @fgonzalez 当然可以,你只需要一个正确的compareTo 实现。
  • 但是如果我只能通过一个字段或另一个字段进行比较,我看不到正确的实现,要么我这样做 return Integer.compare(userId, uh.userId);或者我这样做 return Integer.compare(value,uh.value);在我的 compareTo 实现中。在第一种情况下,键将是唯一的但未排序,在第二种情况下,它们将被排序但重复,明白我的意思吗?
  • 如果您通过两个字段进行比较,则 0,1 和 0,2 不会匹配,您每次都会看到“重复”。 compareTo 方法只返回一个数字,所以你不能做你想做的事,那就是让它说“嗯......它真的是同一个条目但我不希望它在左边”我只会在 userId 上进行比较,然后在 util 方法中让该用户返回后然后比较值
  • 另外,如果您只想为每个用户输入一个条目,为什么要将分值作为键的一部分?关键不应该只是用户ID吗?您不必担心对 userId 值进行排序,因为您一次只希望每个用户有一个值
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-27
  • 2018-07-27
相关资源
最近更新 更多