之前的博文中讲述了JNI的基础知识:
cygwin + gcc+makeFile入门(三): JNI的编译
这两篇文章讲述了JNI最普遍的两个问题, 环境的建立以及参数的传递.
JNI作为连接Java 和 本地C资源的一个非常重要的技术, 需要被好好重视并掌握, 本章将总结一下JNI涉及的多线程问题, 在此之前, 需要再次重申, JNI技术的应用背景:
1. 永远只考虑Java对C代码的调用, Java的优势在于GUI层面, C的优势在于执行效率. Java 涉及的是业务层入口, C考虑的是底层逻辑的执行。两个简单的例子
a.> java技术本身涉及了众多JNI调用, 如AWT/Swing这类绘图,最终是通过JNI调用的, 但Java面向程序员的API, 不会出现C以及接近底层的Natvie方法
b.> Android 技术, 也只可能是Java调用C, 不会反过来, 因为反过来, 跟JNI设计的初衷不符合, 把问题复杂化了.
2. 如果JNI将架构复杂化了, 说明技术上不应该考虑JNI,或者你理解错了.
3. 多考虑文件的接口, 流的接口, 而不是仅仅是对象的接口, 这样会让JNI的设计更加优雅.
现在是JNI多线程的正题, 一个JNI多线程的例子程序, 设计一个多线程, 并且实现同步, 我理解的多线程需求如下:
1. 线程在Java端启动, 两个线程都调用C的方法
2. 有一个共同的数据, 被C的代码修改, 要求线程能对这个修改做同步, 即线程1和线程2不能冲突
可能会有人想: 干嘛不在C这端做多线程以及同步呢? 这个问题跟"干嘛不是C调用Java"类似, C实质上只是一个高效的数据处理器, 把东西传给它处理, 处理完之后, 交给Java就OK. C 中不做复杂的线程同步等操作, 所有的复杂的调度操作, 都应该是上层完成的, C仅仅是傻傻的执行者, 不是管理者。——原谅一个Java Coder的技术情节吧!
【后续可能要利用一些存在C多线程的代码, 这个另当别论. 暂时忽略, 后面有时间再总结这类问题】
通过代码的方式, 我们看看这些是怎么实现的:
1. C中存在一个全局变量value = 1000. 这个将被三个方法调用, 一个加一, 另一个减一. 另外一个查看全局变量的值. 这两个方法提供给外部的Java调用
- package com.ostrichmyself.jni;
- public class HelloWorld {
- public native void addOne();
- public native void minusOne();
- public native int getGlobalValue();
- }
对应的C代码为:
- #include"com_ostrichmyself_jni_HelloWorld.h"
- #include <stdio.h>
- int value = 1000;
- JNIEXPORT void JNICALL Java_com_ostrichmyself_jni_HelloWorld_addOne
- (JNIEnv *env, jobject obj)
- {
- value = value +1;
- }
- JNIEXPORT void JNICALL Java_com_ostrichmyself_jni_HelloWorld_minusOne
- (JNIEnv *env, jobject obj)
- {
- value = value -1;
- }
- JNIEXPORT jint JNICALL Java_com_ostrichmyself_jni_HelloWorld_getGlobalValue
- (JNIEnv *env, jobject obj)
- {
- return value;
- }
2. 组织多线程代码, 让他对C发起多个线程的调用, 并操控全局变量.
- package com.ostrichmyself.jni;
- public class MainCygwin {
- /**
- * @param args
- */
- static{
- System.loadLibrary("hello");
- }
- public static void main(String[] args) {
- HelloWorld hl = new HelloWorld();
- Thread t1 = new Thread(new Add("thread add ", hl));
- Thread t2 = new Thread(new Minus("thread Minus ", hl));
- t1.start();
- t2.start();
- }
- }
- class Minus implements Runnable{
- private String threadName;
- private HelloWorld hl;
- public Minus(String name, HelloWorld hl)
- {
- this.threadName = name;
- this.hl = hl;
- }
- @Override
- public void run() {
- int i = 0;
- while(i < 100)
- {
- i++;
- operation();
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public void operation()
- {
- synchronized(hl)
- {
- //加上前后的两句打印, 是为了观察是否被其它线程入侵
- System.out.println("begin:" + threadName + hl.getGlobalValue());
- hl.minusOne();
- try {
- //让线程进入休眠, 这个时候, 是带着锁的休眠
- //如果其它线程不需要这把锁,这个时候将容易被其他线程入侵
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- hl.minusOne();
- System.out.println("end:" + threadName + hl.getGlobalValue());
- }
- }
- }
- class Add implements Runnable{
- private String threadName;
- private HelloWorld hl;
- public Add(String name, HelloWorld hl)
- {
- this.threadName = name;
- this.hl = hl;
- }
- @Override
- public void run() {
- int i = 0;
- while(i < 10)
- {
- i++;
- operation();
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public void operation()
- {
- synchronized(hl)
- {
- //加上前后的两句打印, 是为了观察是否被其它线程入侵
- System.out.println("begin:" + threadName + hl.getGlobalValue());
- hl.addOne();
- System.out.println("end:" + threadName + hl.getGlobalValue());
- }
- }
- }
同步的变量是hl对象, 可以试一下, 注释掉synchronized(hl) 打印的加法操作和减法操作会出现交叉混乱。
当然, 全局变量可以通过Java端传入, 这样更接近现实中的例子, 不过这个例程已经有足够的代表性。
编译环境:
C: cygwin下gcc编译
Java: Eclipse, Windows
源码地址:
http://download.csdn.net/source/2393959
转载自:http://blog.csdn.net/ostrichmyself/article/details/5623804