android中可以通过jni调用native的方法,那么如果在java中存在多个线程调用native的方法,它的展现形式是如何呢?
先说结论:
native的默认执行与java调用的线程保持一致,即处于同一个线程中。其次,如果多个线程调用native方法,也存在线程不安全的情况,需要解决。
问题示例 c++层提供两个
native
方法,分别是add
和get
int i = 0;
extern "C" JNIEXPORT jstring JNICALL
Java_com_spearbothy_jnidemo_MainActivity_add(
JNIEnv *env,
jobject ) {
++i;
return 0;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_spearbothy_jnidemo_MainActivity_get(
JNIEnv *env,
jobject ) {
return env->NewStringUTF(to_string(i).c_str());
}
add
主要做自增操作,由java
起多个线程调用。
get
主要是再结束之后获取结果,没有直接放到add
中打印的原因是,android瞬间打印多个log,存在log丢失的现象。(后面有时间会找一下原因。)
java层
启动四个线程,调用
native
的add
方法共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
std::mutex g_mutex;
extern "C" JNIEXPORT jstring JNICALL
Java_com_spearbothy_jnidemo_MainActivity_add(
JNIEnv *env,
jobject ) {
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 ) {
// g_mutex.lock();
std::lock_guard 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 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加同一把锁。
作者:Alex_MaHao