1

Studying some answers here about volatile keyword behavior I understood that targeting x86 architecture volatile reads are always happens from main memory which is quite expensive.

Consider next situation: I have a block of code that gets executed by lets say 3 threads. Something like that:

class Foo {
  private Bar bar;

  public Bar read () {
    return bar;
  }
  public void write(Bar bar) {
    this.bar = bar;
  }
}

And also I know that reads happen from 3 different threads every small amount of time (let's say 100ms) and write happens let's say once a year. To stick to a context consider Bar immutable - this question not covers interaction with this object, it's just about reference itself.

Making bar volatile will do it's task - each next read no matter which thread executes this read will end up with correct data. But it comes with a very high cost - each read would be the read from main memory.

UPD: So my question is next: is it any way on a JVM to avoid penalty of read from main memory? Maybe by making reference non volatile and telling threads that cached value may be not valid so this threads read from main memory only once a year but not every 100ms? Or at least make it cheaper than main ram read. Can it also be done in non-blocking fashion? Maybe there is a way to approach it using happens-before?

  • Do you know that this is performance problem in your code? Or are you asking out of curiosity? – Boris the Spider Oct 14 '18 at 20:31
  • @BoristheSpider It's not an actual code and I'd love to have performance problems not larger than this so yes - it's more about curiosity and it's more "am I understand things right" or "do I know enough" questions. – Alexandr Sova Oct 14 '18 at 20:49
  • " I understood that targeting x86 architecture volatile reads are always happens from main memory" then you understood wrongly. Loading a non volatile field in x86? `mov rax, rbx`. Loading a volatile field? Still `mov rax, rbx`. You only need a locking instruction (or mfence) when storing volatile fields and even then we don't ignore all the caches - you can read up on MESI protocol to get an idea of how to keep data synchronized across multiple caches. – Voo Oct 15 '18 at 05:05

2 Answers2

1

If you want your threads to normally reuse the previous value (even if it’s out of date) but occasionally check for updates, just do that:

int hits=0;
Bar cache=foo.read();  // Foo.bar is volatile
while(/*...*/) {
  if(++hits==100) {
    hits=0;
    cache=foo.read();
  }
  cache.doStuff();
}

If you want to package this logic (because not all the lookups come from one function), add a layer of indirection and have a CountFoo per thread so that you aren’t sharing hit counters (or paying for thread-local storage).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
0

use rw_locks see http://tutorials.jenkov.com/java-concurrency/read-write-locks.html ; this allows to have no locking when reading only, and having (hard) locking only when there is a writer

OznOg
  • 4,440
  • 2
  • 26
  • 35
  • Thanks for your answer, but there are still parts that I don't get: * How, using this approach, would my reader threads will find out that value at bar(the ref itself) had changed? Where the read would happen from and why is it so? * Wouldn't this approach lead to even more fee (obtain lock at synchronized keyword, access one at a time because of synchronized, being blocked because of such locks) payed for read access than volatile keyword approach? – Alexandr Sova Oct 14 '18 at 20:04
  • @AlexandrSova no. A read write lock is specifically designed to allow multiple readers to access data concurrently. But you are right that this will be more expensive than nothing - it would involve a memory barrier of some sort. – Boris the Spider Oct 14 '18 at 20:33
  • @BoristheSpider The read itself may be granted to many readers - this is what RWLock was designed for, as I understood. But not the grant code sequence - this code gets executed only by one thread at a time by synchronized keyword guarantee. This is what I meant by "one at a time" statement from previous comment. – Alexandr Sova Oct 14 '18 at 20:57
  • @AlexandrSova where are you seeing the `synchronized` keyword? – Boris the Spider Oct 14 '18 at 20:59
  • @BoristheSpider following the link at the answer each public method have one. – Alexandr Sova Oct 14 '18 at 21:13
  • @AlexendarSova Ah, my bad. I thought the link was to the correct place - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/ReentrantReadWriteLock.html. Ignore the above, the provided Java class is **much** more sophisticated. – Boris the Spider Oct 14 '18 at 21:16
  • The idea that a read write lock should somehow be more efficient than a simple volatile read doesn't make any sense. Think about it: What primitives do you think are used to implement the lock? Using the lock is useful if you need to do more than writing or reading a single value. – Voo Oct 15 '18 at 05:08
  • @Voo don't be silly. There are a plethora of lock types, and a full memory barrier (both a read and a write fence) is not required for many of them. If you think that the only concurrency primitive available in Java is the `synchronized` block then you are mistaken. – Boris the Spider Oct 15 '18 at 06:06
  • 2
    @Boris Yeah what a silly thing that would be. Good thing then that I explicitly said **volatile read** (the thing the question is about) and didn't mention synchronized at all. Volatile reads are the lowest form of guarantee in the JMM (there's some lower stuff in the unsafe packages for HotSpot but that's not guaranteed to be there) so trying to replace them with a lock to improve performance is obviously not going to work. – Voo Oct 15 '18 at 06:22
  • @Voo that’s fair. The JMM requires a write fence before writing to a volatile field and a full fence after - this is rather expensive. Because of this, `AtomicXXX.lazySet` was introduced which doesn’t guarantee immediate visibility but does guarantee ordered writes. It’s also true that great performance improvements can be gained by using unsafe - given the wide spread use, I don’t see that going anywhere. Finally, given the quality of the code in the link in this answer, I’m sure we can both agree that there are better approaches than that... – Boris the Spider Oct 15 '18 at 06:34