What is difference between getXXXVolatile vs getXXX in java unsafe?
Asked Answered
S

2

6

I am trying to understand the two methods here in java unsafe:

   public native short getShortVolatile(Object var1, long var2);

vs

   public native short getShort(Object var1, long var2);

What is the real difference here? What does volatile here really work for? I found API doc here: http://www.docjar.com/docs/api/sun/misc/Unsafe.html#getShortVolatile(Object,%20long)

But it does not really explain anything for the difference between the two functions.

My understanding is that, for volatile, it only matters when we do write. To me, it should make sense that we call putShortVolatile and then for reading, we can simply call getShort() since volatile write already guarantee the new value has been flushed into main memory.

Please kindly correct me if anything is wrong. Thanks!

Scute answered 5/2, 2018 at 3:23 Comment(6)
Interesting question. Have you tried looking for the native implementations for these methods?Reclamation
"for volatile, it only matters when we do write" - that's incorrect - the Java Memory Model section in the JLS describes the real meaning of the volatile modifier. For example for reads, it means that the value cannot be cached in a CPU register. In any case, getShortVolatile can't have the same meaning as the volatile modifier, as only affects the reader. The field itself doesn't need to be volatile, so the writing thread could be caching the value in a CPU register, for example.Olvera
@ErwinBolwidt thanks for the comment, I think you comment is tending to address my confusion. So can you help me to see if this statement is correct? For a particular short value, if the program has only used putShortVolatile() to write, then there is no need to use getShortVolatile() here as we are sure the update must be flushed into memory?Scute
@ErwinBolwidt in your sentence "so the writing thread could be caching the value in a CPU register, for example." do you mean that when we use putShort() for writing?Scute
sun.misc.Unsafe provides no guarantees; it's not part of the Java memory model and it can do things that break the Java memory model. The standard APIs that use sun.misc.Unsafe are in adherence with the Java Memory Model but there is no official documentation to my knowledge on how they do that. Your best bets are: a) don't use sun.misc.Unsafe but use regular reflection to access a volatile field b) use the volatile versions of put and get on Unsafe to be sure. Unless you know the CPU architecture and the possible optimizations done by HotSpot exactly, it's not safe to make assumptions.Olvera
@JacobG. indeed good question, I assumed in my answer that this is plain read of a short with the semantics of a volatile...Prelature
S
6

Here there is an article: http://mydailyjava.blogspot.it/2013/12/sunmiscunsafe.html

Unsafe supports all primitive values and can even write values without hitting thread-local caches by using the volatile forms of the methods

getXXX(Object target, long offset): Will read a value of type XXX from target's address at the specified offset.

getXXXVolatile(Object target, long offset): Will read a value of type XXX from target's address at the specified offset and not hit any thread local caches.

putXXX(Object target, long offset, XXX value): Will place value at target's address at the specified offset.

putXXXVolatile(Object target, long offset, XXX value): Will place value at target's address at the specified offset and not hit any thread local caches.

UPDATE:

You can find more information about memory management and volatile fields on this article: http://cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html (it contains also some example of reordering).

In multiprocessor systems, processors generally have one or more layers of memory cache, which improves performance both by speeding access to data (because the data is closer to the processor) and reducing traffic on the shared memory bus (because many memory operations can be satisfied by local caches.) Memory caches can improve performance tremendously, but they present a host of new challenges. What, for example, happens when two processors examine the same memory location at the same time? Under what conditions will they see the same value?

Some processors exhibit a strong memory model, where all processors see exactly the same value for any given memory location at all times. Other processors exhibit a weaker memory model, where special instructions, called memory barriers, are required to flush or invalidate the local processor cache in order to see writes made by other processors or make writes by this processor visible to others.

The issue of when a write becomes visible to another thread is compounded by the compiler's reordering of code. If a compiler defers an operation, another thread will not see it until it is performed; this mirrors the effect of caching. Moreover, writes to memory can be moved earlier in a program; in this case, other threads might see a write before it actually "occurs" in the program.

Java includes several language constructs, including volatile, final, and synchronized, which are intended to help the programmer describe a program's concurrency requirements to the compiler. The Java Memory Model defines the behavior of volatile and synchronized, and, more importantly, ensures that a correctly synchronized Java program runs correctly on all processor architectures.

As you can see in the section What does volatile do?

Volatile fields are special fields which are used for communicating state between threads. Each read of a volatile will see the last write to that volatile by any thread; in effect, they are designated by the programmer as fields for which it is never acceptable to see a "stale" value as a result of caching or reordering. The compiler and runtime are prohibited from allocating them in registers. They must also ensure that after they are written, they are flushed out of the cache to main memory, so they can immediately become visible to other threads. Similarly, before a volatile field is read, the cache must be invalidated so that the value in main memory, not the local processor cache, is the one seen.

There are also additional restrictions on reordering accesses to volatile variables. Accesses to volatile variables could not be reordered with each other. Is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.

So the difference is that the setXXX() and getXXX() could be reorded or could use cached values not yet synchronized between the threads, while the setXXXVolatile() and the getXXXVolatile() won't be reordered and will use always the last value.

The thread local cache is a temporary storage used from java to improve performances: the data will be written/read into/from the cache before to be flushed on the memory.

In a single thread context you can use both the not-volatile than the volatile version of those methods, there will be no difference. When you write something, it doesn't matter if it is written immediately on memory or just in the thread local cache: when you'll try to read it, you'll be in the same thread, so you'll get the last value for sure (the thread local cache contain the last value).

In a multi thread context, instead, the cache could give you some throubles. If you init an unsafe object, and you share it between two or more threads, each of those threads will have a copy of it into its local cache (the two threads could be runned on different processors, each one with its cache).

If you use the setXXX() method on a thread, the new value could be written in the thread local cache, but not yet in the memory. So it could happens that just one of the multiple thread contains the new value, while the memory and the other threadds local cache contain the old value. This could bring to unexpected results. The setXXXVolatile() method will write the new value directly on memory, so also the other threadds will be able to access to the new value (if they use the getXXXVolatile() methods).

If you use the getXXX() method, you'll get the local cache value. So if another thread has changed the value on the memory, the current thread local cache could still contains the old value, and you'll get unexpeted results. If you use the getXXXVolatile() method, you'll access directly to the memory, and you'll get the last value for sure.

Using the example of the previous link:

class DirectIntArray {
 
  private final static long INT_SIZE_IN_BYTES = 4;
   
  private final long startIndex;
 
  public DirectIntArray(long size) {
    startIndex = unsafe.allocateMemory(size * INT_SIZE_IN_BYTES);
    unsafe.setMemory(startIndex, size * INT_SIZE_IN_BYTES, (byte) 0);
    }
  }
 
  public void setValue(long index, int value) {
    unsafe.putInt(index(index), value);
  }
 
  public int getValue(long index) {
    return unsafe.getInt(index(index));
  }
 
  private long index(long offset) {
    return startIndex + offset * INT_SIZE_IN_BYTES;
  }
 
  public void destroy() {
    unsafe.freeMemory(startIndex);
  }
}

This class use the putInt and the getInt to write the values into an array allocated on the memory (so outside the heap space). As said before, those methods write the data in the thread local cache, not immediately in the memory. So when you use the setValue() method, the local cache will be updated immediately, the allocated memory will be updated after a while (it depends from the JVM implementation). In a single thread context that class will work without problem. In a multi threads context it could fails.

DirectIntArray directIntArray = new DirectIntArray(maximum);
Runnable t1 = new MyThread(directIntArray);
Runnable t2 = new MyThread(directIntArray);
new Thread(t1).start();
new Thread(t2).start();

Where MyThread is:

public class MyThread implements Runnable {
    DirectIntArray directIntArray;
    
    public MyThread(DirectIntArray parameter) {
        directIntArray = parameter;
    }

    public void run() {
        call();
    }
    
    public void call() {
        synchronized (this) {
            assertEquals(0, directIntArray.getValue(0L));  //the other threads could have changed that value, this assert will fails if the local thread cache is already updated, will pass otherwise
            directIntArray.setValue(0L, 10);
            assertEquals(10, directIntArray.getValue(0L));
        }
    }
}

With putIntVolatile() and getIntVolatile(), one of the two threads will fails for sure (the second threads will get 10 instead of 0). With putInt() and getInt(), both the threads could finish with success (because the local cache of both threads could still contains 0 if the writer cache wasn't been flushed or the reader cache wasn't been refreshed).

Swigart answered 5/2, 2018 at 3:31 Comment(15)
Hi @debe, thx for answering! Is the thread-local caches referred to the register cache?Scute
@Swigart that's like saying nothing, I hardly find this an answer :|Prelature
In Java every thread has a local cache. If you write a multi thread application, the same object could have a different values on different threads. You can avoid that problem using volatile writing and reading: the local cache wont be used, and you'll be sure to read the actual value. Here you can get more detailshttps://www.ibm.com/developerworks/library/j-5things15/index.htmlSwigart
@Swigart you should tag someone via @ if you want to make a comment, otherwise there is no way for me to know that you commented. you are seeing this way too simplified btw, there is no mention in your answer of re-orderings, what caches might be used or data in registers, store/load buffers etc. My point was that this does not really answer the question, as why reading a plain field with volatile semantics is needed in the first placePrelature
@Eugene, sorry, I didn't know the @ feature. I'll write more details during the lunch break (more or less between two hours).Swigart
@Swigart I don't know if this is worse or better; first your terminology (understanding) of "local cache" and "main memory" are somehow wrong, what you are probably actually referring is re-orderings and cache coherency protocol. Then I don't understand your point with putIntVolatile and getIntVolatile. Are you saying that once a ThreadA does putIntVolatile and another ThreadB does getIntVolatile it is suppose to see the most recent value that ThreadA set?Prelature
@Eugene, yes, using Volatile method you are sure to get the most recent value. It works both on re-ordering (the write won't be postponed) than on cache flushing (on multiprocessors system each processor has its own L1/L2/L3 cache, access to the RAM memory decrease the performance tremendously, usually it is better to postpone the cache flushing as much as possible). You can read this article: cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html It explay how memory works on multiprocessor systems, and how volatile fields works (see the "What does volatile do?" section).Swigart
@Swigart writing a volatile field from one thread, does not mean that reading it will get the most recent value. if one thread observes the write that other thread did - it means that that thread will see all other writes that were done before that - only this is guaranteed, and this is fundamentally wrong in this answer. and you are messing up other notions quite a lot too...Prelature
@Prelature of course, if you write using a volatile method but you read using a not-volatile method, the read could get a cached old value. Or do you refers to synchronization, join, lock, semaphore, atomic wrappers, thread safe collection, and the other ways to synchronize a multithread application?Swigart
@Swigart Im sorry but I don't understand your comment, at all. you should look at the example that you have provided, where you can see that if you observe that v == true, you are also guaranteed that x == 42. simply writing to volatile field does not mean that other threads that will read, will see the written value, you have to observer that write.Prelature
@Swigart read shipilev.net/blog/2014/on-the-fence-with-dependencies and especially this part: That means once you have properly observed the volatile value, you also have a chance to observe the prior stores — we are building up the implementation of happens-before herePrelature
@Eugene, thanks for the article, I'll read it later. About the example without v == true there are no garanties about x == 42 because x is not volatile. If even x should have been volatile, I think it should have been synchronized even without the reading. Isn't so?Swigart
@Swigart yes, without volatile there are no guarantees that x would be 42, even if v is seen to be truePrelature
@Prelature ok, I agree with you. As explained in the article, x == 42 is guaranteed just because v is volatile and its reading avoid that the statement x=42 is moved after the statement v=true from the reordering.Swigart
@Swigart now you see the basic problem with this answer (at least the comments) when you say that threadA write to a volatile field means that threadB reading will absolutely see the written value - which is wrong. It should be when threadB observes the written valuePrelature
P
1

I think that getShortVolatile is reading a plain short from an Object, but treats it as a volatile; it's like reading a plain variable and inserting the needed barriers (if any) yourself.

Much simplified (and to some degree wrong, but just to get the idea). Release/Acquire semantics:

Unsafe.weakCompareAndSetIntAcquire // Acquire
update some int here
Unsafe.weakCompareAndSetIntRelease // Release

As to why this is needed (this is for getIntVolatile, but the case still stands), is to probably enforce non-reorderings. Again, this is a bit beyond me and Gil Tene explaining this is FAR more suited.

Prelature answered 5/2, 2018 at 8:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.