2

I'm just a non-developer playing to be a developer, so my question may be extremely simple!

I'm just testing Java multi-threading stuff, this is not real code. I wonder how to make two member variables update at the same time in Java, in case we want them both in sync. As an example:

public class Testing
{
  private Map<String, Boolean> itemToStatus = new ConcurrentHashMap<>();
  private Set<String> items = ConcurrentHashMap.newKeySet();

  public static void main(String[] args)
  {
    (new Testing()).start("ABC");
  }

  public void start(String name) {
    if (name.equals("ABC")) {
      itemToStatus.put(name, true);
      items.add(name);
    }
  }
}

In that scenario (imagine multi-threaded, of course) I want to be able to guarantee that any reads of items and itemToStatus always return the same.

So, if the code is in the line itemToStatus.put(name, true), and other thread asks items.contains(name), it will return false. On the other hand, if that other thread asks itemToStatus.containsKey(name); it will return true. And I don't want that, I want them both to give the same value, if that makes sense?

How can I make those two changes atomic? Would this work?

if (name.equals("ABC")) {
    synchronised(this) {
        itemToStatus.put(name, true);
        items.add(name);
    }
}

Still, I don't see why that would work. I think that's the case where you need a lock or something?

Cheers!

Will
  • 181
  • 1
  • 14
  • 1
    multi-threading is not "extremely simple" – CraigR8806 Jan 12 '17 at 18:21
  • You would need that synchronized block around each time you _read_ the objects, as well. – yshavit Jan 12 '17 at 18:21
  • +1 to it not being simple! And along the same lines, it's not something that lends itself to picking up in bits and pieces. Unlike single-threaded coding where you can experiment, with multithreading it's very easy to come up with something that works every time as you test it, and then fails horribly and confusingly 0.07% of the time in the field. If you're interested in learning about it, I would strongly recommend reading a book or similarly in-depth guide. – yshavit Jan 12 '17 at 18:23
  • Check this [answer](http://stackoverflow.com/questions/14851624/using-concurrenthashmap-when-is-synchronizing-necessary) and this [guide](http://crunchify.com/hashmap-vs-concurrenthashmap-vs-synchronizedmap-how-a-hashmap-can-be-synchronized-in-java/) – Gatusko Jan 12 '17 at 18:23
  • @CraigR8806 haha so far programming has been "simple". I was hoping multi-threading is, too :( – Will Jan 12 '17 at 18:24
  • @Gatusko sure I understand that answer, but it takes into account only one variable, not two... What about two variables? – Will Jan 12 '17 at 18:25
  • thanks @yshavit, sounds like I should be doing that, too. Was just curious of how a problem like that one can be fixed. – Will Jan 12 '17 at 18:26
  • Is that you want to add Sync to a ConcurrentHashMap and you can't do that. Because That will be a synchronizedMap. You loose Sync when you use ConcurrentHashMap – Gatusko Jan 12 '17 at 18:28

2 Answers2

1

Just synchronizing the writes won't work. You would also need to synchronize (on the same object) the read access to items and itemToStatus collections. That way, no thread could be reading anything if another thread were in the process of updating the two collections. Note that synchronizing in this way means you don't need ConcurrentHashMap or ConcurrentHashSet; plain old HashMap and HashSet will work because you're providing your own synchronization.

For example:

public void start(String name) {
    if (name.equals("ABC")) {
        synchronized (this) {
            itemToStatus.put(name, true);
            items.add(name);
        }
    }
}

public synchronized boolean containsItem(String name) {
    return items.contains(name);
}

public synchronized boolean containsStatus(String name) {
    return itemToStatus.containsKey(name);
}

That will guarantee that the value returned by containsItem would also have been returned by containsStatus if that call had been made instead. Of course, if you want the return values to be consistent over time (as in first calling containsItem() and then containsStatus()), you would need higher-level synchronization.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • Do you mean adding `synchronised` to the variable declarations? How would that help? Mind providing some sample code? – Will Jan 12 '17 at 18:22
  • Wait, if you add `synchronised` to the method, you guarantee atomic access to the method, right? But that doesn't mean you're not reading an old value, does it? I'm sorry, am I making any sense? – Will Jan 12 '17 at 18:32
  • Like, one thread calling `containsItem`, while another thread is executing `itemToStatus.put(name, true);`, that would lead to unsynchronised state, am I right? – Will Jan 12 '17 at 18:34
  • @Will - With proper synchronization, the thread calling `containsItem()` would be blocked until the other thread finished the update call (or vice versa). It would be important that the entire write logic be synchronized as a single block so as to be treated as atomic. (Likewise, multiple reads, if they need to behave atomically, would need to be synchronized as a single block.) – Ted Hopp Jan 12 '17 at 18:37
  • @Will That's partially what `synchronized` prevents. I would read up on the subject, one really can't a fully grasp the memory model and multi-threading features of Java in a small amount of information. – NESPowerGlove Jan 12 '17 at 18:38
  • Thanks a lot, I think this is the right answer. I'll read more on the subject to fully understand it, but I think it's really making sense now :) – Will Jan 12 '17 at 20:10
0

The short answer is yes: by synchronizing the code block, as you did in your last code snippet, you made the class thread-safe because that code block is the only one that reads or modifies the status of the class (represented by the two instance variables). The meaning of synchronised(this) is that you use the instance of the object (this) as a lock: when a thread enters that code block it gets the lock, preventing other threads to enter the same code block until the thread releases it when it exits from the code block.

  • Thanks for the reply Jacopo. I kind of understand it's thread safe, but if someone else reads the first variable `itemToStatus` while another thread is doing `items.add(name);`, wouldn't that be a problem, even with that `synchronised`? – Will Jan 12 '17 at 18:30
  • Other threads are not prevented from reading the values unless they also synchronize on the same lock object. – Ted Hopp Jan 12 '17 at 18:30
  • Exactly: all the code in the class that reads or modifies the two fields need to be wrapped within a synchronized block using the *same* lock, which in your case is the "this" reference. – Jacopo Cappellato Jan 12 '17 at 18:33
  • Riiiiiight, ok... So by doing `synchronised(this)` you're potentially "locking the whole object" until that block is finished, true? So any other threads doing ANYTHING else anywhere else will stop there and wait until the block is done, is that true? – Will Jan 12 '17 at 18:36
  • Of course, sounds like it can be slow, but please let me know if I'm right with my previous comment – Will Jan 12 '17 at 18:36
  • Not "locking the whole object": only locking the blocks that are synchronized. – Jacopo Cappellato Jan 12 '17 at 18:48