JDK-4023233 : Memory accesses for doubles are not atomic.
  • Type: Enhancement
  • Component: specification
  • Sub-Component: language
  • Affected Version: 1.1.4,1.2.1
  • Priority: P5
  • Status: Closed
  • Resolution: Won't Fix
  • OS: solaris_2.5.1,solaris_2.6
  • CPU: generic,sparc
  • Submitted: 1996-12-20
  • Updated: 2007-07-10
  • Resolved: 2007-07-10
Related Reports
Duplicate :  
Description
allan.jacobs@Eng 1996-12-20

The problem is present in the native thread implementation of the VM.  When
run with THREADS_FLAG not present in the environment, no printout appears
when the class is interpreted.  When THREADS_FLAG is set to native, the
error message "corruptedMemory" is written to standard output.


tarantula% unsetenv THREADS_FLAG
tarantula% javac -d . e1720101.java
tarantula% java X
tarantula% setenv THREADS_FLAG native
tarantula% java X
corruptedMemory
tarantula% cat e1720101.java

/**
 **   JAVATEST : The Modena JAVA Test Suite, Version 2.0, November 1996
 **   Copyright (c) 1996 Modena Software (I) Pvt. Ltd., All Rights Reserved
 **/

/*
 *  Section:  17.2
 *
 *  Filename: c1720101.java
 *
 *  Purpose:  Positive test for section 17.2, para 1:
 *
 *           "The actions performed by the main memory for any one variable
 *            are totally ordered; that is, for any two actions performed
 *            by the main memory on the same variable, one action precedes
 *            the other."
 *
 *           Section 17.4 para 2:
 *           "This matters because a read or write of a double or long 
 *            variable may be handled by an actual main memory as two
 *            32-bit read or write actions that may be separated in time,
 *            with other actions coming between them."
 *
 *
 *  Language Specification:  October, 1996
 */


//
// Note: When run on JDK1_0_2 for NT4.0/Win95, this test randomly fails runtime
//       execution. It is advisable that this test is manually run multiple
//       times to ensure the results.
//
//
// Test Description: Several threads simultaneously read/write on the
//                   same variable for lakhs of times. Variable should
//                   not have any other value except from the union of
//                   various predetermined values written by all the threads.
//
// Treats volatile double
//

class T1 extends Thread {
  final static double d1=Double.MAX_VALUE;
  public void run()
  {
    for(int i=0;i<500000;i++)
     {
       double dTemp=X.d;
       if((dTemp!=T1.d1)&&(dTemp!=T2.d2)&&(dTemp!=T3.d3)&&(dTemp!=T4.d4)&&(dTemp!=T5.d5))
        {
        //
        // stop all the threads, since X.d has unexpected value
        //
          X.corruptedMemory=true;
          X.t2.stop();
          X.t3.stop();
          X.t4.stop();
          X.t5.stop();
        }
       else
         X.d=d1;
     }
  }
}

class T2 extends Thread {
  final static double d2=Double.MIN_VALUE;
  public void run()
  {
    for(int i=0;i<500000;i++)
     {
       double dTemp=X.d;
       if((dTemp!=T1.d1)&&(dTemp!=T2.d2)&&(dTemp!=T3.d3)&&(dTemp!=T4.d4)&&(dTemp!=T5.d5))
        {
        //
        // stop all the threads, since X.d has unexpected value
        //
          X.corruptedMemory=true;
          X.t1.stop();
          X.t3.stop();
          X.t4.stop();
          X.t5.stop();
        }
       else
         X.d=d2;
     }
  }
}

class T3 extends Thread {
  final static double d3=0.0;
  public void run()
  {
    for(int i=0;i<500000;i++)
     {
       double dTemp=X.d;
       if((dTemp!=T1.d1)&&(dTemp!=T2.d2)&&(dTemp!=T3.d3)&&(dTemp!=T4.d4)&&(dTemp!=T5.d5))
        {
        //
        // stop all the threads, since X.d has unexpected value
        //
          X.corruptedMemory=true;
          X.t1.stop();
          X.t2.stop();
          X.t4.stop();
          X.t5.stop();
        }
       else
         X.d=d3;
     }
  }
}

class T4 extends Thread {
  final static double d4=999999;
  public void run()
  {
    for(int i=0;i<500000;i++)
     {
       double dTemp=X.d;
       if((dTemp!=T1.d1)&&(dTemp!=T2.d2)&&(dTemp!=T3.d3)&&(dTemp!=T4.d4)&&(dTemp!=T5.d5))
        {
        //
        // stop all the threads, since X.d has unexpected value
        //
          X.corruptedMemory=true;
          X.t1.stop();
          X.t2.stop();
          X.t3.stop();
          X.t5.stop();
        }
       else 
         X.d=d4;
     }
  }
}

class T5 extends Thread {
  final static double d5=-777777;
  public void run()
  {
    for(int i=0;i<500000;i++)
     {
       double dTemp=X.d;
       if((dTemp!=T1.d1)&&(dTemp!=T2.d2)&&(dTemp!=T3.d3)&&(dTemp!=T4.d4)&&(dTemp!=T5.d5))
        {
        //
        // stop all the threads, since X.d has unexpected value
        //
          X.corruptedMemory=true;
          X.t1.stop();
          X.t2.stop();
          X.t3.stop();
          X.t4.stop();
        }
       else 
         X.d=d5;
     }
  }
}

class X 
{
  static boolean corruptedMemory=false;
  static Thread t1=new T1();
  static Thread t2=new T2();
  static Thread t3=new T3();
  static Thread t4=new T4();
  static Thread t5=new T5();
  static volatile double d=T1.d1;
  public static void main(String argv[]) throws InterruptedException
   {
     try {
       t1.start();
       t2.start();
       t3.start();
       t4.start();
       t5.start();
       t1.join();
       t2.join();
       t3.join();
       t4.join();
       t5.join();
     }
     catch(Throwable e) { 
       corruptedMemory=true;
       System.err.println(e); // something very wrong 
     }
     if (corruptedMemory) {
        System.out.println("corruptedMemory");
     }
   }
}

Comments
EVALUATION The spec issue is better explained in 4067775. Namely, it would be nice if all 64-bit values (even those in non-volatile variables) were written atomically, such that JLS 17.7 could be removed. However, this issue was not touched by JSR-133 and it is extremely unlikely that it ever will be.
29-05-2007

EVALUATION The problem is that 8 byte datums are dealt with in two operations, each operation dealing with 4 bytes (the JVM word atom). On green threads there can be no context switch between each 4 byte access, whereas with native threads there is true concurrency, and there can be a context switch between the put/get of the first 4 bytes, and the put/get of the second 4 bytes, with the result that an 8 byte datum is not atomically dealt with (and hence violates the volatile constraint). By this we mean that one half came from one putstatic, and another half from a different putstatic operation, we have a 8 byte value that was never actually stored. 8 byte puts to a volatile class variable (.s and .c versions) : OP0(putstatic2_quick) ldub [PC+2],T2 sll NextByte,10,NextByte ldub [PC+3],Opcode ! fetchahead add NextByte,ConstP,T1 ld [SP-8],TOSA sll T2,2,T2 ! scale for addressing ld [T1+T2],T1 ! fieldblock pointer dec 8,SP ld [SP+4],TOSB ld [T1+STRUCT_OFFSET(fieldblock,u.static_address)],T1 ldub [PC+4],NextByte ! fetchahead SCALE(Opcode) ! have address, put data. st TOSA,[T1] st TOSB,[T1+4] GO inc 3,PC case opc_putstatic2_quick: { struct fieldblock *fb = constant_pool[GET_INDEX(pc+1)].fb; stack_item *location = (stack_item *)twoword_static_address(fb); location[0] = S_INFO(-2); location[1] = S_INFO(-1) 8 byte get from a static class variable: OP0(getstatic2_quick) ldub [PC+2],T2 sll NextByte,10,NextByte ldub [PC+3],Opcode ! fetchahead add NextByte,ConstP,T1 sll T2,2,T2 ! scale for addressing ld [T1+T2],T1 ! fieldblock pointer SCALE(Opcode) ld [T1+STRUCT_OFFSET(fieldblock,u.static_address)],TOSA ldub [PC+4],NextByte ! fetchahead ! have address, get data. ld [TOSA+4],TOSB inc 3,PC ld [TOSA],TOSA inc 8,SP st TOSB,[SP-4] GO st TOSA,[SP-8] case opc_getstatic2_quick: { struct fieldblock *fb = constant_pool[GET_INDEX(pc+1)].fb; stack_item *location = (stack_item *)twoword_static_address(fb); S_INFO(0) = location[0]; S_INFO(1) = location[1]; antony.davies@Canada 1996-12-26 In green threads there *can* be a context switch between accesses if a signal occurrs. dean.long@Eng 1997-04-04 If this bug is too hard to fix by 1.2, we should lower its priority. sheng.liang@Eng 1997-08-06 Recast to specification per CQI bug meeting. mohammad.gharahgouzloo@Eng 2000-02-25 I don't see the spec issue here. The spec requires that accesses to volatile doubles and longs be atomic. This should not change. The implementation needs to be fixed. gilad.bracha@eng 2000-04-24
24-04-2000

SUGGESTED FIX The suggest fix applies only to static volatile access, ie the field attribute contains the ACC_VOLATILE attribute (ACC_THREADSAFE in the code). 1) If the first four bytes are aligned on an 8 byte boundary, do a double word access to put/get from a local 'C' variable that is a an 8 byte quantity (I dont know about x86, but sparc guarantess doubleword accesses are atomic) 2) else resort to a mutex_t to ensure atomic put/get operations ---- Not all Sparc C compilers will generate doubleword accesses. The Sunpro compiler generates 32-bit accesses for the 'long long' type. For the C interpreter loop, or on machines without atomic 64-bit memory accesses, synchronization or disabling of context switches is probably required. dean.long@Eng 1997-04-04
04-04-1997