Android jni 线程同步

Posted Alex_MaHao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android jni 线程同步相关的知识,希望对你有一定的参考价值。

文章目录

概述

android中可以通过jni调用native的方法,那么如果在java中存在多个线程调用native的方法,它的展现形式是如何呢?

先说结论:

native的默认执行与java调用的线程保持一致,即处于同一个线程中。其次,如果多个线程调用native方法,也存在线程不安全的情况,需要解决。

问题示例

c++层

提供两个native方法,分别是addget

int i = 0;

extern "C" JNIEXPORT jstring JNICALL
Java_com_spearbothy_jnidemo_MainActivity_add(
        JNIEnv *env,
        jobject /* this */) 
    ++i;
    return 0;


extern "C" JNIEXPORT jstring JNICALL
Java_com_spearbothy_jnidemo_MainActivity_get(
        JNIEnv *env,
        jobject /* this */) 
    return env->NewStringUTF(to_string(i).c_str());

add主要做自增操作,由java起多个线程调用。

get主要是再结束之后获取结果,没有直接放到add中打印的原因是,android瞬间打印多个log,存在log丢失的现象。(后面有时间会找一下原因。)

java层

启动四个线程,调用nativeadd方法共40000次,并打印最终结果。

    private Handler mMainHandler = new Handler() 
        @Override
        public void handleMessage(@NonNull Message msg) 
            cout++;
            if (cout > 3) 
                // 输出结果
                Log.i("jni", " c++ -> " + get());
            
        
    ;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startMainLoop();
        startThreadLoop();
        startThreadLoop();
        startThreadLoop();
    

   private void startMainLoop() 
        logNative();
    

    private void startThreadLoop() 
        new Thread(new Runnable() 
            @Override
            public void run() 
                logNative();
            
        ).start();
    

    private void logNative() 
        for (int i = 0; i < 10000; i++) 
            add();
        
        mMainHandler.sendEmptyMessage(1);
    

    public native void add();

    public native String get();

结果

如果运行多次,可能存在线程不安全的情况,最终i的值不是40000

jni:  c++ -> 39670

解决办法

java层加锁

logNative()add()前后加锁

 private void logNative() 
        for (int i = 0; i < 10000; i++) 
            synchronized (mLock) 
                add();
            
        
        mMainHandler.sendEmptyMessage(1);
    

此处加锁的方式有两种:

  • 一种是直接加在方法上,即private synchronized void .. (耗时20ms)
  • 还有一种更细粒度的。通过验证,更细粒度的执行效率会更低。因为频繁的加锁解锁。(耗时100ms)

c++层加锁

#include <thread>
std::mutex g_mutex;
extern "C" JNIEXPORT jstring JNICALL
Java_com_spearbothy_jnidemo_MainActivity_add(
        JNIEnv *env,
        jobject /* this */) 

    g_mutex.lock();
    ++i;
    g_mutex.unlock();
    return 0;

mutex是c++ 11 推出的相关功能。使用上和java的Lock十分相似。

经过验证,在c++1层加锁的效率更高(耗时26ms)。

c++也提供了不需要解锁的自动解锁机制。

extern "C" JNIEXPORT jstring JNICALL
Java_com_spearbothy_jnidemo_MainActivity_add(
        JNIEnv *env,
        jobject /* this */) 

//    g_mutex.lock();
    std::lock_guard<decltype(g_mutex)> lock(g_mutex);
    
    ++i;
//    g_mutex.unlock();

    return 0;

decltype是获取对象的类型,该处和java的泛型类似。

lock_guard在构造函数中自动加锁,在析构函数中解锁。

java层和c++层共用一个锁

修改logNative(),分别加锁。

 private void logNative() 
        index++;
        for (int i = 0; i < 10000; i++) 
            if (index > 3) 
                synchronized (mLock) 
                    add();
                
             else 
                addLock(mLock);
            
        
        mMainHandler.sendEmptyMessage(1);
    
    
public native void addLock(Object lock);

mLock为锁对象,只在其中一个线程以java的方式进行加锁,同时另一部分在c++中加锁。

extern "C" JNIEXPORT jstring JNICALL
Java_com_spearbothy_jnidemo_MainActivity_addLock(
        JNIEnv *env,
        jobject lock) 

    (*env).MonitorEnter(lock);
    ++i;
    (*env).MonitorExit(lock);
    return 0;

mLock对象传入,这样就实现了c++和java加同一把锁。

以上是关于Android jni 线程同步的主要内容,如果未能解决你的问题,请参考以下文章

Android jni 线程同步

如何在android的jni线程中实现回调

JNI——Android Native中跨线程使用JNI的问题

如何在android的jni线程中实现回调

深入了解android平台的jni---本地多线程调用java代码

Android 系统开发Android JNI 之 JNIEnv 解析