9
votes

I wrote this simple Java program:

package com.salil.threads;

public class IncrementClass {

    static volatile int j = 0;
    static int i = 0;

    public static void main(String args[]) {

        for(int a=0;a<1000000;a++);
        i++;
        j++;            
    }       
}

This generate the following disassembled code for i++ and j++ (remaining disassembled code removed):

  0x0000000002961a6c: 49ba98e8d0d507000000 mov       r10,7d5d0e898h
                                                ;   {oop(a 'java/lang/Class' = 'com/salil/threads/IncrementClass')}
  0x0000000002961a76: 41ff4274            inc       dword ptr [r10+74h]
                                                ;*if_icmpge
                                                ; - com.salil.threads.IncrementClass::main@5 (line 10)
  0x0000000002961a7a: 458b5a70            mov       r11d,dword ptr [r10+70h]
  0x0000000002961a7e: 41ffc3              inc       r11d
  0x0000000002961a81: 45895a70            mov       dword ptr [r10+70h],r11d
  0x0000000002961a85: f083042400          lock add  dword ptr [rsp],0h
                                                ;*putstatic j
                                                ; - com.salil.threads.IncrementClass::main@27 (line 14)

This is what I understand about the following assembly code:

  • mov r10,7d5d0e898h : Moves the pointer to the IncrementClass.class to register r10
  • inc dword ptr [r10+74h] : Increments the 4 byte value at the address at [r10 + 74h],(i.e. i)
  • mov r11d,dword ptr [r10+70h] :Moves the 4 value value at the address [r10 + 70h] to register r11d (i.e move value of j to r11d)
  • inc r11d : Increment r11d
  • mov dword ptr [r10+70h],r11d : write value of r11d to [r10 + 70h] so it is visible to other threads -lock add dword ptr [rsp],0h : lock the memory address represented by the stack pointer rsp and add 0 to it.

JMM states that before each volatile read there must be a load memory barrier and after every volatile write there must be a store barrier. My question is:

  1. Why isn't there a load barrier before the read of j into r11d?
  2. How does the lock and add to rsp ensure the value of j in r11d is propogated back to main memory. All I read from the intel specs is that lock provides the cpu with an exclusive lock on the specified memory address for the duration of the operation.
3
That code is super-bad. lock inc dword [r10+70h] would do everything that load/inc/store/full-barrier does, and more (i.e. actually be atomic). It would be at least as fast, and many fewer code bytes. lock add [rsp], 0 is a full-barrier because every locked instruction is. There's debate about whether MFENCE or an otherwise no-op locked insn to stack memory (which should be in the E state in L1 already) is better. MFENCE has worse throughput, but fewer uops so maybe less impact on surrounding instructions when a chain of MFENCE isn't all you're doing. - Peter Cordes
mov r10, imm64 is also suspicious. That's inside the loop??? Is this optimized code from a JIT? Is inc r11d the loop counter, or is that at least kept in a register? - Peter Cordes
@SalilSurendran I know this is old but shouldn't the statement be after each volatile read there must be a load memory barrier? after the read, not before - Eugene

3 Answers

7
votes

Intel Processor x86 has a strong memory model.

Therefore all barrier StoreStore , LoadLoad, LoadStore are no-op on x86. Except StoreLoad which can be realized via mfence or cpuid or locked insn. Which you can already confirm with your assembly code. Other barriers just mean restriction to compilers optimization and transformation so that they don't break java memory model spec.

As you ran on intel Processor i am assuming its x86.

Please read

  1. http://gee.cs.oswego.edu/dl/jmm/cookbook.html for reference.

  2. http://psy-lob-saw.blogspot.com/2013/08/memory-barriers-are-not-free.html

  3. http://jsr166-concurrency.10961.n7.nabble.com/x86-NOOP-memory-barriers-td9991.html

Lock is not an instruction but moreof a instruction prefix (behaves as a storeLoad barrier).

  1. What does the "lock" instruction mean in x86 assembly?
  2. Why we need lock prefix before CMPXCHG
1
votes

volatile keyword in Java only guarantee that the thread local copies and caches would be skipped and the value would be loaded directly from main memory or write to main memory. However it doesn't contains locking mechanism. Thus reading from volatile, or writing to volatile, is atomic, but a series of read and write operations, like your above

j++

is NOT atomic, because some other thread can modify the value of j between the read and write to the variable in main memory. To achieve atomic increment you need to use CAS operations which is wrapped in Atomic classes in java, like AtomicInteger etc. Alternatively, if you prefer low level programming, you can use atomic methods in Unsafe class E.g. Unsafe.compareAndSwapInt etc.

0
votes

The barrier may be optimized out by your JIT compiler since your program is single-threaded(there is only a thread-the main thread), just like a lock under a single-threaded environment can be optimized out. This optimization is independent of processor architecture.