Eve Application Development

Michael L Brereton - 06 March 2008, http://www.ewesoft.com/

 

Advanced Guide - Contents

<< Previous: Eve Native Interface

Advanced ENI Topics

More Efficient Java Synchronization.. 1

 

More Efficient Java Synchronization

 

In the previous chapter we showed how you could use synchronization between a Java Object and an ENI function. During the course of the ENI function activity, it would update the value of Java fields to indicate its progress. The problem with this approach is that when the ENI function attempts to set the Java fields, it must block until it is allowed access to the Java VM. Under a true Java VM this is not that much of a problem but because of the limitations of the Eve VM, this may cause unpredictable delays that could be detrimental to the ENI function.

 

The way around this is to use two threads – one is used by the ENI function to do the time critical tasks and one is used to retrieve status data from the ENI function and pass it on to the Java VM. The time critical ENI function will now synchronize using local C++ objects to the status monitor thread. That monitoring thread, when it detects a change, then accesses the Java VM and updates the status information in the Java object. The monitoring thread may block when accessing the Java VM, but the time critical task will never block on the Java VM.

 

To synchronize between two (and only two) threads in an ENI DLL without using the Java VM we use the class ThreadMonitor as shown below:

 

class ThreadMonitor {
public:
  ThreadMonitor();
 ~ThreadMonitor(); 
 void ref();
 void unref();
 void lock();
 void unlock();
 bool wait(int64 millis = -1, int nanos = 0);
 void notify();
};

 

A ThreadMonitor works similarly to a standard Java Monitor. You call lock() to exclusively gain a lock on the monitor and unlock() to release the lock, and you can call wait() and notify() only if you currently own the monitor.

 

o       It should not be considered re-entrant. Therefore a single thread that calls lock() may not be able to call lock() again before calling unlock(). Although it may be re-entrant on some platforms, on others it will not be.

o       The notify() method will only notify a single waiting Thread. Therefore you should only use this monitor to synchronize two threads.

o       Use ref() to add one to the reference count of the ThreadMonitor (it is always created with a reference count of 1) and unref() to decrement the reference count. When the reference count reaches zero the ThreadMonitor is deleted. This scheme allows for multiple threads to refer to and release the ThreadMonitor without worrying about whether the other thread is still using it.

Another class called SimpleLock provides the simplest locking system available on the platform.

 

class SimpleLock {
public:
   SimpleLock();
 ~SimpleLock();
 void lock();
 void unlock();
};

 

There is no notify() or wait() available in this class and again it is not re-entrant. This should only be used for the simplest of critical section locking.

 

A third class called Synchronized is used to safely lock() and unlock() a ThreadMonitor without risk of forgetting to unlock(). Once the Synchronized object is in scope the ThreadMonitor is locked to the current thread and once it goes out of scope the ThreadMonitor is released. You can also call notify() and wait() on it and the calls are passed to the ThreadMonitor it is synchronizing.

 

// Do some unsynchronised work.
doSomethingNonCritical();
// Now we need to synchronize with aThreadMonitor.
{
 Synchronized s(aThreadMonitor);
 if (aCondition){
       doSomething();
       return true;
 }
 s.notify();
 return true;
}

 

In the example above the section that begins with the Synchronized value will be synchronized to aThreadMonitor until execution leaves the scope. Note that control leaves the section through two different paths depending on whether aCondition is true or not. In any case, the s variable goes out of scope once execution leaves the section and this automatically unlocks the monitor.

 

Now we can use these to do more advanced ENI Thread synchronizing.

 

package eveeni;
import eve.sys.Vm;
 
public class AdvancedProcessTest extends Thread{
 
 private int progress = -1;
 private boolean complete;
 // This will be used to hold a native pointer.
 // Make it package protected not private so it is not optimized away.
 int nativeData;
 
 private native void doProcess();
 private native void monitorProgress();
 private native void setupNative();
 private native void cleanupNative();
 
 public AdvancedProcessTest()
 {
       setupNative();
 }
 protected void finalize()
 {
       cleanupNative();
 }
 public void waitAndReport() throws InterruptedException
 {
       int lastProgress = -1;
       while(true){
              int p = 0;
              //
              // Wait up to 1/10 of a second for some progress.
              // If none, print out a '.'
              //
              synchronized(this){
                    //
                    // Don't make any blocking calls except wait()
                    // while synchronizing with an ENI thread.
                    //
                    if (progress == lastProgress && !complete)
                          wait(100);
                    p = progress;
              }
              //
              if (p != lastProgress){
                    lastProgress = p;
                    System.out.println("Progress: "+lastProgress);
              }else{
                    System.out.print(".");
                    System.out.flush();
              }
              if (complete) break;
       }
       System.out.println("Complete!");
 }
 public void run()
 {
       System.out.println("Native Thread Starting.");
       // Here we run the thread that monitors the native process
       // and will pass the values to this Java object.
       new Thread(){
              public void run(){
                    monitorProgress();
              }
       }.start();
       //
       doProcess();
       System.out.println("Native Thread Done.");
 }
 
 public static void main(String[] args) throws Exception
 {
       Vm.startEve(args);
       System.loadLibrary("test_eni");
       AdvancedProcessTest pt = new AdvancedProcessTest();
       pt.start();
       pt.waitAndReport();
 }
}

 

This class AdvancedProcessTest is very similar to the ProcessTest class in the previous chapter except for the following additions:

1.      There is a method to set up some native data when the class is created (setupNative()).

2.      There is a method to clean up the native data when the class is finalized (cleanupNative).

3.      There is a new Thread that is run in parallel with doProcess() called monitorProgress().

 

Now we will create the ENI program to implement this class. First we create a simple utility class to hold the progress and complete status values and which conveniently lets us check if they have changed.

 

//============================================================
class processProgress {
//============================================================
public:
 int progress;
 bool complete;
 processProgress(){progress = -1; complete = false;}
 
 bool hasChanged(processProgress &isNow)
 {
       if (isNow.progress != progress) return true;
       if (isNow.complete != complete) return true;
       return false;
 }
 bool getChanged(processProgress &isNow)
 {
       if (!hasChanged(isNow)) return false;
       progress = isNow.progress;
       complete = isNow.complete;
       return true;
 }
//============================================================
};
//============================================================

 

Next we’ll incorporate this class into a ThreadMonitor to produce a new class we’ll call processSync.

 

//============================================================
class processSync : public ThreadMonitor{
//============================================================
public:
 processProgress progress;
//============================================================
};
//============================================================

 

We will use a processSync class to synchronize between the time critical ENI method, doProcess(), and the monitoring method, monitorProgress(). The monitorProgress() thread will also synchronize with the Java VM using the AdvancedProcessTest object itself.

 

First we’ll create a single utility function that will create, fetch and destroy a processSync class and store a reference to it in the nativeData field of the AdvancedProcessTest object.

 

#define SYNC_GET 0
#define SYNC_SETUP 1
#define SYNC_DESTROY -1
 
//--------------------------------------------------------------------------------------
static processSync *getSync(EniVm & vm,ObjectReference thisObject,int syncOp = SYNC_GET)
//--------------------------------------------------------------------------------------
{
 static SaveAField(nativeData,int);
 LiveVm java(vm);
 LiveReference thiz = java[thisObject];
 processSync *p = NULL;
 
 if (syncOp == SYNC_SETUP){ 
       //Create a new one.
       p = new processSync();
       thiz[nativeData] = (int)p;
       return p;
 }else{ 
       //Fetch or destroy an existing one.
       jint nd = thiz[nativeData];
       p = (processSync *)nd;
       if (syncOp != SYNC_DESTROY) return p;
       if (p != NULL) p->unref();
       thiz[nativeData] = 0;
       return NULL;
 }
}

 

Now implementing setupNative() and cleanupNative() is simple:

 

//============================================================
void eveeni_AdvancedProcessTest_setupNative(EniVm & vm,ObjectReference thisObject)
//============================================================
{
 getSync(vm,thisObject,SYNC_SETUP);
}
//------------------------------------------------------------
eve_eni_link(instance,void,eveeni_AdvancedProcessTest_setupNative,0,())
//------------------------------------------------------------
//============================================================
void eveeni_AdvancedProcessTest_cleanupNative(EniVm & vm,ObjectReference thisObject)
//============================================================
{
 getSync(vm,thisObject,SYNC_DESTROY);
}
//------------------------------------------------------------
eve_eni_link(instance,void,eveeni_AdvancedProcessTest_cleanupNative,0,())
//------------------------------------------------------------

 

Next we’ll implement monitorProgress.

 

//============================================================
void eveeni_AdvancedProcessTest_monitorProgress(EniVm & vm,ObjectReference thisObject)
//============================================================
{
 static SaveAField(progress,int);
 static SaveAField(complete,int);
 // We’ll keep a view of what the progress was last,
 // and compare it with the current progress in the processSync object.
 processProgress myProgress;
 processSync *ps = getSync(vm,thisObject);
 
 do{
       // Synchronize with the processSync object and wait for a change.
       {
              Synchronized s(*ps);
              bool changed = false;
              while(!myProgress.complete && !changed){
                    changed = myProgress.getChanged(ps->progress);
                    if (!changed) s.wait();
              }
       }
       // A Change has occured or it is complete.
       // Pass on the new status to the Java object.
       {
              LiveVm java(vm);
              LiveReference thiz = java[thisObject];
              {
                    SynchronizedObject js(thiz);
                    thiz[progress] = myProgress.progress;
                    thiz[complete] = myProgress.complete;
                    js.notify();
              }
       }
       // Leave when the ENI thread is done.
 }while(!myProgress.complete);
}
//------------------------------------------------------------
eve_eni_link(instance,void,eveeni_AdvancedProcessTest_monitorProgress,0,())
//------------------------------------------------------------

 

Now we’ll implement the doProcess() method. It is very similar to the simpler ProcessTest version, but instead of synchronizing with the Java object, it synchronizes with the processSync object instead.

 

//============================================================
void eveeni_AdvancedProcessTest_doProcess(EniVm & vm,ObjectReference thisObject)
//============================================================
{
 processSync *ps = getSync(vm,thisObject);
 //
 // From this point on I don't have to synchronize with the Java VM
 // at all.
 //
 int max = 20;
 for (int i = 0; i<max; i++){
       Sleep(50);
       {
              Synchronized s(*ps);
              ps->progress.progress = (i+1);
              ps->progress.complete = i == max-1;
              s.notify();
       }
 }
}
//------------------------------------------------------------
eve_eni_link(instance,void,eveeni_AdvancedProcessTest_doProcess,0,())
//------------------------------------------------------------

 

That completes the full implementation.