Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is difference between getXXXVolatile vs getXXX in java unsafe?

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!

like image 971
Lubor Avatar asked Feb 05 '18 03:02

Lubor


2 Answers

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).

like image 123
debe Avatar answered Oct 08 '22 17:10

debe


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.

like image 36
Eugene Avatar answered Oct 08 '22 18:10

Eugene