Search code examples
javamultithreadingjava-native-interfacethread-synchronization

Notify Java from new JNI thread


I made a Java program which calls JNI, and in JNI it creates a "JNICallback" thread and executes in it env-attachCurrentThreadAsDaemon()

My goal is to notify from JNICallback thread, the another pure Java thread. I do standard things like synchronized -> wait, synchronized -> notifyAll, using static final Object as a sync. If I run it under Eclipse, everything works fine.

But if I export it as a "runnable jar file" and run under same JVM, I see my JNI program invokes monitorEnter, notifyAll, monitorExit with no errors, but Java doesnt notified. In the debugger I see different ObjectId's for the very same object used from JNI and from Java, this is suspicious for me (it is names onConnectEvent in the code below) I even move notification code from JNI to Java, and use a static void method call from JNI, doing synchronize->notifyAll in Java, but this only shows me that waiting thread waits on different ObjectId, so I guess this is the reason.

...
    static final Object onConnectEvent = new Object();
...
    Thread dogServerConnect,...;
...
    public MainClass(){
        dogServerConnect = runDog(new ServerConnectionWatchDog(), "onServerConnect");
        someJNIstuffPlusNewThreadsCreation();
...
    
    static void onJNICallbackEvent(int type) {
        switch (type) {
        case 1 -> {
            synchronized(onConnectEvent) {
                onConnectEvent.notifyAll();
                 ^^^^^ got to this line from JNI, just fine
            }
...
    class ServerConnectionWatchDog implements Runnable {
        @Override
        public void run() {
            while(true)
                synchronized(onConnectEvent) {
                    try {
                        onConnectEvent.wait();
                        for (var l : listeners) {
^^^^^^^^^^^^^^^^^^^^^^^^^ never come to this line
...

again, this is really works well under Eclipse, but for some reason fails as separate jar file. Why could same final static object have a different instance ids for different threads, or it's just fine nowadays?

I mean for the waiting thread Eclipse shows me "waiting for: Object (id=48)" and for the native thread on before "notifyAll" execution it shows me "owns: Object (id=50)". Maybe this is because I manually pause the waiting thread, to see object id (then run it again, this doesn't the cause) What could I miss?


Solution

  • Found the answer in the FAQ for Android: why didn't FindClass find my class. Although my environment is not Android, it still makes sence. The thing is that env->FindClass unwinds a Java stack to find a caller class and use it's classloader. And the Java stack is empty for the native threads which started in JNI. So this gives us an only options either get a global ref to the required classes before creating the native thread, or to get in the same place a reference to it's classloader and then use a FindClass from saved classLoader, which is harder. This solution prevents the Eclipse JarRsrcLoader from creation of duplicate static objects. Probably one might say that doing env->FindClass in newly created native thread in JNI is a bad design, at least use it with caution.