Michael L Brereton - 06 March 2008, http://www.ewesoft.com/
<<
Previous: Eve Native Interface
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.