0
final class NameVolatile {
  @Nullable private volatile String name;

  void setName(String name) {
    this.name = name
  }

  void run() {
    String name = name;
    if (name != null) {
      print(name);
    }
  }
}

final class NameSynchronized {
  private final Object guard = new Object();
  @Nullable private String name;

  void setName(String name) {
    synchronized(guard) {
      this.name = name
    }
  }

  void run() {
    synchronized(guard) {
      if (name != null) {
        print(name);
      }
    }
  }
}

The above is an example of two ways to accomplish almost the same thing, but I do not have a good grasp on when to prefer one or the other.

What are the scenarios where one is more useful than the other?

I believe this question is different from Volatile or synchronized for primitive type? because the question and answers there do not mention the practice of having a local cache variable.

Community
  • 1
  • 1
Eric Cochran
  • 8,414
  • 5
  • 50
  • 91
  • Possible duplicate of [Volatile or synchronized for primitive type?](http://stackoverflow.com/questions/1779258/volatile-or-synchronized-for-primitive-type) – 4castle Sep 15 '16 at 03:19
  • 1
    I recommend instead using an [`AtomicReference`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html) – 4castle Sep 15 '16 at 03:23
  • Wouldn't that have the same downside as a volatile without the local cache? It could be set in between the null check and the print statement. – Eric Cochran Sep 15 '16 at 03:34
  • In the print statement you would use [`this.name.get()`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html#get--) in order to get a local cache. – 4castle Sep 15 '16 at 03:47
  • in your example, there's no difference what to use, they work the same way in a low level code – AdamSkywalker Sep 15 '16 at 12:30

1 Answers1

1

use volatile and cache locally or synchronize?

I think you are mistaken about what the volatile keyword does. With all modern OS processors, there is a local per-processor memory cache. The volatile keyword doesn't enable the local cacheing -- you get that "for free" as part of modern computer hardware. This cacheing is an important part of multi-threaded program performance increases.

The volatile keyword ensures that when you read the field, a read memory-barrier is crossed ensuring that all updated central memory blocks are updated in the processor cache. A write to a volatile field means that a write memory-barrier is crossed ensuring that local cache updates are written to central memory. This behavior is exactly the same as the memory barriers that you cross in a synchronized block. When you enter a synchronized block, a read memory-barrier is crossed and when you leave a write is crossed.

The biggest difference between synchronized and volatile is that you pay for locking with synchronized. synchronized is necessary when there are more than a single operation happening at the same time and you need to wrap the operations in a mutex lock. If you are just trying to keep your name field properly updated with main memory then volatile is the way to go.

Another option is an AtomicReference which wraps a private volatile Object field and provides atomic methods like compareAndSet(...). Even if you are not using the special methods, many programmers feel that it is a good way to encapsulate the fields you need to be memory synced.

Lastly, both volatile and synchronized also provide "happens-before" guarantees which control instruction reordering which is important to ensure proper order of operations in your program.

In terms of your code, you should never do something like:

synchronized(guard) {
   if (name != null) {
       print(name);
   }
}

You don't want to do expensive IO inside of a synchronized block. It should something like:

// grab a copy of the name so we can do the print outside of the sync block
String nameCopy;
synchronized(guard) {
   nameCopy = name;
}
if (nameCopy != null) {
   print(nameCopy);
}

With volatile, you want to do one volatile field lookup so something like the following is recommended:

void run() {
   // only do one access to the expensive `volatile` field
   String nameCopy = name;
   if (nameCopy != null) {
      print(nameCopy);
   }
}

Lastly, from the comments, volatile is significantly more expensive than a normal operation (which can use cached memory) but volatile is significantly less expensive than a synchronized block which has to test and update the lock status on the way in and way out of the block and cross the memory barriers that affect all cached memory. My back of the envelope testing shows that demonstrates this performance difference.

Gray
  • 115,027
  • 24
  • 293
  • 354
  • Cool, yeah, I understand the basics you pointed out; I was just interested in the two styles I listed above. That's a really good point about expensive operations in the block; I was just using that as an example, but thanks for the reminder. Your second snippet is my second solution, it looks like. So, I think (barring the `print` being inside the block) my solutions are pretty equivalent, then, right? – Eric Cochran Sep 15 '16 at 18:07
  • Equivalent in terms of memory synchronization and instruction ordering. Not at all equivalent in terms of performance @EricCochran. – Gray Sep 16 '16 at 03:57
  • The performance difference is just because of the I/O implied in print, though, right? Any other performance differences? – Eric Cochran Sep 16 '16 at 04:30
  • 1
    No, the locking is much more expensive than just a memory barrier @EricCochran. `synchronized` is more expensive than `volatile`. Synchronized has to test and update the lock's status in addition to updating the cached memory. My quick tests show that volatile is 10x faster although YMMV. – Gray Sep 16 '16 at 15:38