简体   繁体   中英

How to use the same hashmap in multiple threads

I have a Hashmap that is created for each "mailer" class and each "agent" class creates a mailer. My problem is that each of my "agents" creates a "mailer" that in turn creates a new hashmap. What I'm trying to do is to create one Hashmap that will be used by all the agents(every agent is a thread).

This is the Agent class:

public class Agent implements Runnable {

    private int id;
    private int n;
    private Mailer mailer;
    private static int counter;

    private List<Integer> received = new ArrayList<Integer>();

    @Override
    public void run() {
        System.out.println("Thread has started");
        n = 10;
        if (counter < n - 1) {
            this.id = ThreadLocalRandom.current().nextInt(0, n + 1);
            counter++;
        }
        Message m = new Message(this.id, this.id);
        this.mailer.getMap().put(this.id, new ArrayList<Message>());
        System.out.println(this.mailer.getMap());
        for (int i = 0; i < n; i++) {
            if (i == this.id) {
                continue;
            }
            this.mailer.send(i, m);
        }

        for (int i = 0; i < n; i++) {
            if (i == this.id) {
                continue;
            }
            if (this.mailer.getMap().get(i) == null) {
                continue;
            } else {
                this.received.add(this.mailer.readOne(this.id).getContent());
            }

        }
        System.out.println(this.id + "" + this.received);
    }
}

This is the Mailer class :

public class Mailer {

   private HashMap<Integer, List<Message>> map = new HashMap<>();

   public void send(int receiver, Message m) {
       synchronized (map) {

           while (this.map.get(receiver) == null) {
               this.map.get(receiver);
           }
           if (this.map.get(receiver) == null) {

           } else {
               map.get(receiver).add(m);
           }
       }

   }

   public Message readOne(int receiver) {
       synchronized (map) {
           if (this.map.get(receiver) == null) {
               return null;
           } else if (this.map.get(receiver).size() == 0) {
               return null;
           } else {
               Message m = this.map.get(receiver).get(0);
               this.map.get(receiver).remove(0);
               return m;
           }
       }
   }

   public HashMap<Integer, List<Message>> getMap() {
       synchronized (map) {
           return map;
       }
   }

}

I have tried so far : Creating the mailer object inside the run method in agent.

Going by the idea (based on your own answer to this question) that you made the map static, you've made 2 mistakes.

do not use static

static means there is one map for the entire JVM you run this on. This is not actually a good thing: Now you can't create separate mailers on one JVM in the future, and you've made it hard to test stuff.

You want something else: A way to group a bunch of mailer threads together (these are all mailers for the agent), but a bit more discerning than a simple: "ALL mailers in the ENTIRE system are all the one mailer for the one agent that will ever run".

A trivial way to do this is to pass the map in as argument. Alternatively, have the map be part of the agent, and pass the agent to the mailer constructor, and have the mailer ask the agent for the map every time.

this is not thread safe

Thread safety is a crucial concept to get right, because the failure mode if you get it wrong is extremely annoying: It may or may not work, and the JVM is free to base whether it'll work right this moment or won't work on the phase of the moon or the flip of a coin: The JVM is given room to do whatever it feels like it needs to, in order to have a JVM that can make full use of the CPU's powers regardless of which CPU and operating system your app is running on.

Your code is not thread safe .

In any given moment, if 2 threads are both referring to the same field, you've got a problem: You need to ensure that this is done 'safely', and the compiler nor the runtime will throw errors if you fail to do this, but you will get bizarre behaviour because the JVM is free to give you caches, refuse to synchronize things, make ghosts of data appear, and more.

In this case the fix is near-trivial: Use java.util.concurrent.ConcurrentHashMap instead, that's all you'd have to do to make this safe.

Whenever you're interacting with a field that doesn't have a convenient 'typesafe' type, or you're messing with the field itself (one thread assigns a new value to the field, another reads it - you don't do that here, there is just the one field that always points at the same map, but you're messing with the map) - you need to use synchronized and/or volatile and/or locks from the java.util.concurrent package and in general it gets very complicated. Concurrent programming is hard.

我能够通过在代理类中将邮件程序更改为静态来解决这个问题

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM