How do you properly synchronize threads on the native side of a JNI environment?

Loduwijk :

Question Brief

I am using C++ and Java in one process via JNI. For the use case in question, both a C++ thread and a Java thread are accessing the same data, they are doing so on the C++ side, and I want to properly synchronize the access.

So far, almost all of my JNI thread synchronization has been on the Java side where the answer is obvious: use the provided Java concurrency package and the built-in concurrency language features. Unfortunately, the answer is not so obvious on the C++ side.

What I've Tried so far Brief

I tried using a pthreads mutex thinking that it might work even though I'm not using pthreads to create threads, but that occasionally gets stuck when trying to lock - I'll show an example of that farther below.

Question Details

In my current, specific usage, c++ is polling for changes provided by Java on a 1 second timer (not what I'd like, but I'm not sure how I would make it event-driven given the nature of the legacy c++ code). The Java thread provides data by calling a native function and c++ copies the data into a c++ structure.

This is the type of situation in code (happens on 2 threads, Thread1 and Thread2):

Code Example

Note quite an SSCCE, as it's missing definitions for TheData and TheDataWrapper, but it doesn't really matter what they contain. Assume they merely contain a couple of public ints if that helps your thought process (though, in my case, it's actually multiple arrays of int and arrays of float).

C++:

class objectA
{
    void poll();
    void supplyData(JNIEnv* jni, jobject jthis, jobject data);
    TheDataWrapper cpp_data;
    bool isUpdated;

    void doStuff(TheDataWrapper* data);
};

// poll() happens on a c++ thread we will call Thread1
void objectA :: poll()
{
    // Here, both isUpdated and cpp_data need synchronization

    if(isUpdated)
    {
        do_stuff(&cpp_data);
        isUpdated = false;
    }
}

// supplyData happens on the Thread2, called as a native function from a java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
    // some operation happens that copies the java data into a c++ equivalent
    // in my specific case this happens to be copying ints/floats from java arrays to c++ arrays
    // this needs to be synchronized
    cpp_data.copyFrom(data);
    isUpdated = true;
}

Java:

class ObjectB
{
    // f() happens on a Java thread which we will call Thread2
    public void f()
    {
        // for the general case it doesn't really matter what the data is
        TheData data = TheData.prepareData();
        supplyData(data);
    }

    public native void supplyData(TheData data);
}

What I've Tried so far Details

When I tried pthread's locking as below, sometimes execution gets stuck in pthread_mutex_lock. There should not be a deadlock in this situation, but just to test further I ran a scenario where supplyData was not getting called at all (no data was being supplied), so no deadlock should have been possible, yet the first call to poll will occasionally hang anyway. Perhaps using a pthreads mutex is not a good idea in this situation? Or perhaps I did something stupid and keep overlooking it.

So far, I tried using pthreads as below:

Code Example

C++:

class objectA
{
    pthread_mutex_t dataMutex;
    ... // everything else mentioned before
}

// called on c++ thread
void objectA :: poll()
{
    pthread_mutex_lock(&dataMutex);

    ... // all the poll stuff from before

    pthread_mutex_unlock(&dataMutex);
}

// called on java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
    pthread_mutex_lock(&dataMutex);

    ... // all the supplyData stuff from before

    pthread_mutex_unlock(&dataMutex);
}

Another option I thought about but have not done

I also considered using JNI to call back into java to request a lock using java's concurrency control. That should work, as either thread should block on the java side as needed. However, since accessing java from c++ is overly verbose, I was hoping to avoid going through that headache. I probably could make a c++ class which encapsulates JNI calls into java to request a java lock; that would simplify c++ code, though I wonder about the overhead of crossing back and forth over JNI just for thread locks.

It seems this is not necessary, per the comment by @Radiodef. It appears JNI includes MonitorEnter/MonitorExit functions which already handle the locking on the c++ side. There are pitfalls when using these at the same time as conventional locks on the java side, so please read here before using. I will be trying this out, and I expect that MonitorEnter/MonitorExit will be the answer and I recommend @Radiodef make an answer out of the comment.

Closing

How could I properly synchronize this? Should pthread_mutex_(un)lock work? If not, what can I use to synchronize between the C++ thread and the Java thread?

No JNI-specific C++ code is provided here since the JNI bridge is working and I can pass data back and forth. The question is specifically about proper synchronization between c++/java threads that are otherwise correctly communicating.

As mentioned before, I would prefer to avoid the polling scheme, but that might end up being another question. The legacy c++ code displays its part of the user interface in X/motif, and if I recall correctly the c++ thread above happens to be the event thread for display. The java thread will end up being the java event dispatch thread once the java user interface for this class is plugged in, though for now the java thread is an automated test thread; either way, it's a separate java thread.

The C++ thread is attached to the JVM. In fact, that is the C++ thread that created the JVM, so it should be attached by default.

I have been successful with plugging in other Java user interface elements into this program, but this is the first time C++ has needed non-atomic data from Java which needed to be synchronized. Is there a generally accepted correct way to do this?

Loduwijk :

If both threads are attached to the JVM, then you can access the JNI's synchronization via JNIEnv's MonitorEnter(jobject) and MonitorExit(jobject) functions. Just as it sounds, MonitorEnter aquires a lock on the provided jobject, and MonitorExit releases the lock on the provided jobject.

NOTE: There are some pitfalls to be aware of! Notice the second to last paragraph of MonitorEnter's description and the last paragraph of MonitorExit's description about mixing and matching MonitorEnter/MonitorExit with other similar mechanisms which you might otherwise think are compatible.

See here

MonitorEnter

jint MonitorEnter(JNIEnv *env, jobject obj);

Enters the monitor associated with the underlying Java object referred to by obj. Enters the monitor associated with the object referred to by obj. The obj reference must not be NULL. Each Java object has a monitor associated with it. If the current thread already owns the monitor associated with obj, it increments a counter in the monitor indicating the number of times this thread has entered the monitor. If the monitor associated with obj is not owned by any thread, the current thread becomes the owner of the monitor, setting the entry count of this monitor to 1. If another thread already owns the monitor associated with obj, the current thread waits until the monitor is released, then tries again to gain ownership.

A monitor entered through a MonitorEnter JNI function call cannot be exited using the monitorexit Java virtual machine instruction or a synchronized method return. A MonitorEnter JNI function call and a monitorenter Java virtual machine instruction may race to enter the monitor associated with the same object.

To avoid deadlocks, a monitor entered through a MonitorEnter JNI function call must be exited using the MonitorExit JNI call, unless the DetachCurrentThread call is used to implicitly release JNI monitors.

LINKAGE:

Index 217 in the JNIEnv interface function table.

PARAMETERS:

env: the JNI interface pointer.

obj: a normal Java object or class object.

RETURNS:

Returns “0” on success; returns a negative value on failure.

and

MonitorExit

jint MonitorExit(JNIEnv *env, jobject obj);

The current thread must be the owner of the monitor associated with the underlying Java object referred to by obj. The thread decrements the counter indicating the number of times it has entered this monitor. If the value of the counter becomes zero, the current thread releases the monitor.

Native code must not use MonitorExit to exit a monitor entered through a synchronized method or a monitorenter Java virtual machine instruction.

LINKAGE:

Index 218 in the JNIEnv interface function table.

PARAMETERS:

env: the JNI interface pointer.

obj: a normal Java object or class object.

RETURNS:

Returns “0” on success; returns a negative value on failure.

EXCEPTIONS:

IllegalMonitorStateException: if the current thread does not own the monitor.

So the C++ code in the question which attempted to use pthreads should be changed as following (code assumes the JNIEnv* pointer was acquired somehow beforehand in typical JNI fashion):

class objectA
{
    jobject dataMutex;
    ... // everything else mentioned before
}

// called on c++ thread
void objectA :: poll()
{
    // You will need to aquire jniEnv pointer somehow just as usual for JNI
    jniEnv->MonitorEnter(dataMutex);

    ... // all the poll stuff from before

    jniEnv->MonitorExit(dataMutex);
}

// called on java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
    // You will need to aquire jniEnv pointer somehow just as usual for JNI
    jniEnv->MonitorEnter(dataMutex);

    ... // all the supplyData stuff from before

    jniEnv->MonitorExit(dataMutex);
}

Kudos to @Radiodef who provided the answer. Unfortunately it was as a comment. I waited until afternoon next day to allow time for Radiodef to make it an answer, so now I'm doing it. Thank you Radiodef for providing the nudge I needed to fix this.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=435095&siteId=1