1.传递字符串
到目前为止,我们还没有实现Java程序向C程序传递参数,或者C程序向Java程序传递参数。本例程将由Java程序向C程序传入一个字符串,C程序对该字符串转成大写形式后回传给Java程序。
Java源程序如下。
代码清单15-6 在Linux平台上调用C函数的例程——Sample1
public class Sample1
{
public native String stringMethod(String text);
public static void main(String[] args)
{
System.loadLibrary("Sample1");
Sample1 sample = new Sample1();
String text = sample.stringMethod("Thinking In Java");
System.out.println("stringMethod: " + text);
}
}
Sample1.java以“Thinking In Java”为参数调用libSample1.so中的函数stringMethod(),在得到返回的字符串后打印输出。
Sample1.c的源程序如下。
代码清单15-7 在Linux平台上调用C函数的例程——Sample1.c
#include <Sample1.h>
#include <string.h>
JNIEXPORT jstring JNICALL Java_Sample1_stringMethod(JNIEnv *env, jobject obj, jstring string)
{
const char *str = (*env)->GetStringUTFChars(env, string, 0);
char cap[128];
strcpy(cap, str);
(*env)->ReleaseStringUTFChars(env, string, str);
int i=0;
for(i=0;i<strlen(cap);i++)
*(cap+i)=(char)toupper(*(cap+i));
return (*env)->NewStringUTF(env, cap);
}
首先请注意函数头部分,函数接收一个jstring类 型的输入参数,并输出一个jstring类型的参数。jstring是jni.h中定义的数据类型,是JNI框架内特有的字符串类型,因为jni.h在 Sample1.h中被引入,因此在Sample1.c中无须再次引入。
程序的第4行是从JNI调用上下文中获取UTF编码的输入字符,将其放在指针str所指向 的一段内存中。第9行是释放这段内存。第13行是将经过大写转换的字符串予以返回,这一句使用了NewStringUTF()函数,将C语言的字符串指针 转换为JNI的jstring类型。JNIEnv也是在jni.h中定义的,代表JNI调用的上下文,GetStringUTFChars()、 ReleaseStringUTFChars()和NewStringUTF()均是JNIEnv的函数。
2.递整型数组
本节例程将首次尝试在JNI框架内启用数组:C程序向Java程序返回一个定长的整型数组成的数组,Java程序将该数组打印输出。
Java程序的源代码如下。
代码清单15-8 在Linux平台上调用C函数的例程——Sample2
public class Sample2
{
public native int[] intMethod();
public static void main(String[] args)
{
System.loadLibrary("Sample2");
Sample2 sample=new Sample2();
int[] nums=sample.intMethod();
for(int i=0;i<nums.length;i++)
System.out.println(nums[i]);
}
}
Sample2.java调用libSample2.so中的函数intMethod()。Sample2.c的源代码如下。
代码清单15-9 在Linux平台上调用C函数的例程——Sample2.c
#include <Sample2.h>
JNIEXPORT jintArray JNICALL Java_Sample2_intMethod(JNIEnv *env, jobject obj)
{
inti = 1;
jintArray array;//定义数组对象
array = (*env)-> NewIntArray(env, 10);
for(; i<= 10; i++)
(*env)->SetIntArrayRegion(env, array, i-1, 1, &i);
/* 获取数组对象的元素个数 */
int len = (*env)->GetArrayLength(env, array);
/* 获取数组中的所有元素 */
jint* elems = (*env)-> GetIntArrayElements(env, array, 0);
for(i=0; i<len; i++)
printf("ELEMENT %d IS %d\n", i, elems[i]);
return array;
}
Sample2.c涉及了两个jni.h定义的整型数 相关的数据类型:jint和jintArray,jint是在JNI框架内特有的整数类型。程序的第7行开辟出一个长度为10 的jint数组。然后依次向该数组中放入元素1-10。第11行至第16行不是程序的必须部分,纯粹是为了向读者们演示GetArrayLength() 和GetIntArrayElements()这两个函数的使用方法,前者是获取数组长度,后者则是获取数组的首地址以便于遍历数组。
3. 传递字符串数组
本节例程是对上节例程的进一步深化:虽然仍然是传递数组,但是数组的基类换成了字符串这样一种对象数据类型。Java程序将向C程序传入一个包含中文字符的字符串,C程序并没有处理这个字符串,而是开辟出一个新的字符串数组返回给Java程序,其中还包含两个汉字字符串。
Java程序的源代码如下。
代码清单15-10 在Linux平台上调用C函数的例程——Sample3
public class Sample3
{
public native String[] stringMethod(String text);
public static void main(String[] args) throws java.io.UnsupportedEncodingException
{
System.loadLibrary("Sample3");
Sample3 sample = new Sample3();
String[] texts = sample.stringMethod("java编程思想");
for(int i=0;i<texts.length;i++)
{
texts[i]=new String(texts[i].getBytes("ISO8859-1"),"GBK");
System.out.print( texts[i] );
}
System.out.println();
}
}
Sample3.java调用libSample3.so中的函数stringMethod()。Sample3.c的源代码如下:
代码清单15-11 在Linux平台上调用C函数的例程——Sample3.c
#include <Sample3.h>
#include <string.h>
#include <stdlib.h>
#define ARRAY_LENGTH 5
JNIEXPORT jobjectArray JNICALL Java_Sample3_stringMethod(JNIEnv *env, jobject obj, jstring string)
{
jclass objClass = (*env)->FindClass(env, "java/lang/String");
jobjectArray texts= (*env)->NewObjectArray(env, (jsize)ARRAY_LENGTH, objClass, 0);
jstring jstr;
char* sa[] = { "Hello,", "world!", "JNI", "很", "好玩" };
int i=0;
for(;i<ARRAY_LENGTH;i++)
{
jstr = (*env)->NewStringUTF( env, sa[i] );
(*env)->SetObjectArrayElement(env, texts, i, jstr);//必须放入jstring
}
return texts;
}
第9、10行是我们需要特别关注的地方:JNI框架并 没有定义专门的字符串数组,而是使用jobjectArray——对象数组,对象数组的基类是jclass,jclass是JNI框架内特有的类型,相当 于Java语言中的Class类型。在本例程中,通过FindClass()函数在JNI上下文中获取到java.lang.String的类型 (Class),并将其赋予jclass变量。
在例程中我们定义了一个长度为5的对象数组texts,并在程序的第18行向其中循环放入预先定义好的sa数组中的字符串,当然前置条件是使用NewStringUTF()函数将C语言的字符串转换为jstring类型。
本例程的另一个关注点是C程序向Java程序传递的中文字符,在Java程序中能否正常显 示的问题。在笔者的试验环境中,Sample3.c是在Linux平台上编辑的,其中的中文字符则是用支持GBK的输入法输入的,而Java程序采用 ISO8859_1字符集存放JNI调用的返回字符,因此在“代码清单15-10在Linux平台上调用C函数的例程——Sample3”的第14行中将 其转码后输出。
4.传递对象数组
本节例程演示的是C程序向Java程序传递对象数组,而且对象数组中存放的不再是字符串,而是一个在Java中自定义的、含有一个topic属性的MailInfo对象类型。
MailInfo对象定义如下。
代码清单15-12 在Linux平台上调用C函数的例程——MailInfo
public class MailInfo {
public String topic;
public String getTopic()
{
return this.topic;
}
public void setTopic(String topic)
{
this.topic=topic;
}
}
Java程序的源代码如下。
代码清单15-13 在Linux平台上调用C函数的例程——Sample4
public class Sample4
{
public native MailInfo[] objectMethod(String text);
public static void main(String[] args)
{
System.loadLibrary("Sample4");
Sample4 sample = new Sample4();
MailInfo[] mails = sample.objectMethod("Thinking In Java");
for(int i=0;i<mails.length;i++)
System.out.println(mails[i].topic);
}
}
Sample4.java调用libSample4.so中的objectMethod()函数。Sample4.c的源代码如下。
代码清单15-14 在Linux平台上调用C函数的例程——Sample4.c
#include <Sample4.h>
#include <string.h>
#include <stdlib.h>
#define ARRAY_LENGTH 5
JNIEXPORT jobjectArray JNICALL Java_Sample4_objectMethod(JNIEnv *env, jobject obj, jstring string)
{
jclass objClass = (*env)->FindClass(env, "java/lang/Object");
jobjectArray mails= (*env)->NewObjectArray(env, (jsize)ARRAY_LENGTH, objClass, 0);
jclass objectClass = (*env)->FindClass(env, "MailInfo");
jfieldID topicFieldId = (*env)->GetFieldID(env, objectClass, "topic", "Ljava/lang/String;");
int i=0;
for(;i<ARRAY_LENGTH;i++)
{
(*env)->SetObjectField(env, obj, topicFieldId, string);
(*env)->SetObjectArrayElement(env, mails, i, obj);
}
return mails;
}
程序的第9、10行读者们应该不会陌生,在上一节的例 程中已经出现过,不同之处在于这次通过FindClass()函数在JNI上下文中获取的是java.lang.Object的类型(Class),并将 其作为基类开辟出一个长度为5的对象数组,准备用来存放MailInfo对象。
程序的第12、13行的目的则是创建一个jfieldID类型的变量,在JNI中,操作对 象属性都是通过jfieldID进行的。第12行首先查找得到MailInfo的类型(Class),然后基于这个jclass进一步获取其名为 topic的属性,并将其赋予jfieldID变量。
程序的第18、19行的目的是循环向对象数组中放入jobject对象。 SetObjectField()函数属于首次使用,该函数的作用是向jobject的属性赋值,而值的内容正是Java程序传入的jstring变量 值。请注意在向对象属性赋值和向对象数组中放入对象的过程中,我们使用了在函数头部分定义的jobject类型的环境参数obj作为中介。至此,JNI框 架固有的两个环境入参env和obj,我们都有涉及。
5.传递byte[]
源C
int SmsSend(char *phonenum,char *contnet);
java的native本地
public native static int SmsSend(byte[] mobileNo,byte[] smContent);
JNIEXPORT jint JNICALL Java_Sample5_objectMethod(JNIEnv *env, jclass jobject, jbyteArray mobileno,jbyteArray smscontent){
char *pSmscontent;
//jsize *theArrayLengthJ=(*env)->GetArrayLength(env,mobileno);
jbyte *arrayBody=(*env)->GetByteArrayElements(env,moblieno,0);
char *pMobileNo=(char *)arrayBody;
printf("[%s]\n,pMobileNo);
//jsize *theArrayLengthJ=(*env)->GetArrayLength(env,smscontent);
arrayBody=(*env)->GetByteArrayElements(env,smscontent,0);
pSmscontent=(char *)arrayBody;
printf("[%s]\n,pSmscontent);
return SmsSend(pMobileNo,pSmscontent);
Java语言支持两种成员(field):(static)静态成员和实例成员. 在JNI获取和赋值成员
的方法是不同的.
Java:
class InstanceFieldAccess {
private String s;
private native void accessField();
public static void main(String args[]) {
InstanceFieldAccess c = new InstanceFieldAccess();
c.s = "abc";
c.accessField();
System.out.println("In Java:");
System.out.println(" c.s = \"" + c.s + "\"");
}
static {
System.loadLibrary("InstanceFieldAccess");
}
}
JNI:
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj)
{
jfieldID fid; /* store the field ID */
jstring jstr;
const char *str;
/* Get a reference to obj’s class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* Look for the instance field s in cls */
fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
if (fid == NULL) {
return; /* failed to find the field */
}
/* Read the instance field s */
jstr = (*env)->GetObjectField(env, obj, fid);
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL) {
return; /* out of memory */
}
printf(" c.s = \"%s\"\n", str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
/* Create a new string and overwrite the instance field */
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL) {
return; /* out of memory */
}
(*env)->SetObjectField(env, obj, fid, jstr);
}
输出:
In C:
c.s = "abc"
In Java:
c.s = "123"
访问对象成员分两步,首先通过GetFieldID得到对象成员ID, 如下:
fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
示例代码,通过GetObjectClass从obj对象得到cls.
这时,通过在对象上调用下述方法获得成员的值:
jstr = (*env)->GetObjectField(env, obj, fid);
示例中要得到的是一个对象类型,所以用GetObjectField. 此外JNI还提供
Get/SetIntField,Get/SetFloatField访问不同类型成员。
Java:
class StaticFielcdAccess {
private static int si;
private native void accessField();
public static void main(String args[]) {
StaticFieldAccess c = new StaticFieldAccess();
StaticFieldAccess.si = 100;
c.accessField();
System.out.println("In Java:");
System.out.println(" StaticFieldAccess.si = " + si);
}
static {
System.loadLibrary("StaticFieldAccess");
}
}
JNI:
JNIEXPORT void JNICALL
Java_StaticFieldAccess_accessField(JNIEnv *env, jobject obj)
{
jfieldID fid; /* store the field ID */
jint si;
/* Get a reference to obj’s class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* Look for the static field si in cls */
fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
if (fid == NULL) {
return; /* field not found */
}
/* Access the static field si */
si = (*env)->GetStaticIntField(env, cls, fid);
printf(" StaticFieldAccess.si = %d\n", si);
(*env)->SetStaticIntField(env, cls, fid, 200);
}
输出:
In C:
StaticFieldAccess.si = 100
In Java:
StaticFieldAccess.si = 200
Java中有三类方法:实例方法、静态方法和构造方法。
class InstanceMethodCall {
private native void nativeMethod();
private void callback() {
System.out.println("In Java");
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("InstanceMethodCall");
}
}
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
{
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return; /* method not found */
}
printf("In C\n");
(*env)->CallVoidMethod(env, obj, mid);
}
输出:
In C
In Java
如上节示例,回调Java方法分两步:
• 首先通过GetMethodID在给定类中查询方法. 查询基于方法名称和签名
• 本地方法调用CallVoidMethod,该方法表明被调Java方法的返回值为void
译者:
从JNI调用实例方法命名规则:Call<Return Value Type>Method
Calling Static Methods
同实例方法,回调Java静态方法分两步:
• 首先通过GetStaticMethodID在给定类中查找方法
• 通过CallStatic<ReturnValueType>Method调用
静态方法与实例方法的不同,前者传入参数为jclass,后者为jobject
Calling Instance Methods of a Superclass
调用被子类覆盖的父类方法: JNI支持用CallNonvirtual<Type>Method满足这类需求:
• GetMethodID获得method ID
• 调用CallNonvirtualVoidMethod, CallNonvirtualBooleanMethod
上述,等价于如下Java语言的方式:
super.f();
CallNonvirtualVoidMethod可以调用构造函数
Invoking Constructors
你可以像调用实例方法一样,调用构造方法,只是此时构造函数的名称叫做"<init>". 如
下构造java.lang.String对象(JNI为了方便有个对应的NewString做下面所有工作,这里
只是做示例展示):
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jmethodID cid;
jcharArray elemArr;
jstring result;
stringClass = (*env)->FindClass(env, "java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
/* Get the method ID for the String(char[]) constructor */
cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */
}
/* Create a char[] that holds the string characters */
elemArr = (*env)->NewCharArray(env, len);
if (elemArr == NULL) {
return NULL; /* exception thrown */
}
(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
/* Construct a java.lang.String object */
result = (*env)->NewObject(env, stringClass, cid, elemArr);
/* Free local references */
(*env)->DeleteLocalRef(env, elemArr);
(*env)->DeleteLocalRef(env, stringClass);
return result;
}
首先,FindClass找到java.lang.String的jclass. 接下来,用GetMethodID找到构造
函数String(char[] chars)的MethodID. 此时用NewCharArray分配一个Char数组对象。
NewObject调用构造函数。
用DeleteLocalRef释放资源。
注意NewString是个常用函数,所以在JNI中直接被支持了,并且该函数的实现要比我们
实现的高效。
也可使用CallNonvirtualVoidMehtod调用构造函数. 如下代码:
result = (*env)->NewObject(env, stringClass, cid, elemArr);
可被替换为:
result = (*env)->AllocObject(env, stringClass);
if (result) {
(*env)->CallNonvirtualVoidMethod(env, result, stringClass, cid,
elemArr);
/* we need to check for possible exceptions */
if ((*env)->ExceptionCheck(env)) {
(*env)->DeleteLocalRef(env, result);
result = NULL;
}
}
AllocObject创建一个未初始化的对象,该函数必须在每个对象上被调用一次而且只能是
一次。
有时你会发现先创建未初始化对象再调用构造函数的方法是有用的。
Caching at the Point of Use
如下,首次使用时,缓存的局部静态变量中,避免每次调用计算。
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj)
{
static jfieldID fid_s = NULL; /* cached field ID for s */
jclass cls = (*env)->GetObjectClass(env, obj);
jstring jstr;
const char *str;
if (fid_s == NULL) {
fid_s = (*env)->GetFieldID(env, cls, "s",
"Ljava/lang/String;");
if (fid_s == NULL) {
return; /* exception already thrown */
}
}
printf("In C:\n");
jstr = (*env)->GetObjectField(env, obj, fid_s);
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL) {
return; /* out of memory */
}
printf(" c.s = \"%s\"\n", str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL) {
return; /* out of memory */
}
(*env)->SetObjectField(env, obj, fid_s, jstr);
}
如上,静态变量fid_s保存了InstanceFieldAccess.s的filed ID。初始化阶段静态变量
被赋值为NULL。第一调用InstanceFieldAccess.accessField时,缓存fieldID以待后用。
你可能会发现上述代码有个竞争条件,当多个线程同时访问此函数时,可能会同时计算一
个field ID. 没关系,此处的竞争是无害的,因为即使在多个线程中同时计算该field
ID,各线程中的计算结果都是一样的。
构造函数的MethodID也可被缓存,如下:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jcharArray elemArr;
static jmethodID cid = NULL;
jstring result;
stringClass = (*env)->FindClass(env, "java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
/* Note that cid is a static variable */
if (cid == NULL) {
/* Get the method ID for the String constructor */
cid = (*env)->GetMethodID(env, stringClass,
"<init>", "([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */
}
}
/* Create a char[] that holds the string characters */
elemArr = (*env)->NewCharArray(env, len);
if (elemArr == NULL) {
return NULL; /* exception thrown */
}
(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
/* Construct a java.lang.String object */
result = (*env)->NewObject(env, stringClass, cid, elemArr);
/* Free local references */
(*env)->DeleteLocalRef(env, elemArr);
(*env)->DeleteLocalRef(env, stringClass);
return result;
}
线程是程序运行的基本执行单元。当操作系统(不包括单线程的操作系统,如微软早期的DOS)在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少建立一个线程(这个线程被称为主线程)来作为这个程序运行的入口点。因此,在操作系统中运行的任何程序都至少有一个主线程。
进程和线程是现代操作系统中两个必不可少的运行模型。在操作系统中可以有多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程);一个进程中可以有一个或多个线程。进程和进程之间不共享内存,也就是说系统中的进程是在各自独立的内存空间中运行的。而一个进程中的线可以共享系统分派给这个进程的内存空间。
线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈, 是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。
注意:任何一个线程在建立时都会执行一个函数,这个函数叫做线程执行函数。也可以将这个函数看做线程的入口点(类似于程序中的main函数)。无论使用什么语言或技术来建立线程,都必须执行这个函数(这个函数的表现形式可能不一样,但都会有一个这样的函数)。如在Windows中用于建立线程的API函数CreateThread的第三个参数就是这个执行函数的指针。
在操作系统将进程分成多个线程后,这些线程可以在操作系统的管理下并发执行,从而大大提高了程序的运行效率。虽然线程的执行从宏观上看是多个线程同时执行,但实际上这只是操作系统的障眼法。由于一块CPU同时只能执行一条指令,因此,在拥有一块CPU的计算机上不可能同时执行两个任务。而操作系统为了能提高程序的运行效率,在一个线程空闲时会撤下这个线程,并且会让其他的线程来执行,这种方式叫做线程调度。我们之所以从表面上看是多个线程同时执行,是因为不同线程之间切换的时间非常短,而且在一般情况下切换非常频繁。假设我们有线程A和B。在运行时,可能是A执行了1毫秒后,切换到B后,B又执行了1毫秒,然后又切换到了A,A又执行1毫秒。由于1毫秒的时间对于普通人来说是很难感知的,因此,从表面看上去就象A和B同时执行一样,但实际上A和B是交替执行的。
二、线程给我们带来的好处
如果能合理地使用线程,将会减少开发和维护成本,甚至可以改善复杂应用程序的性能。如在GUI应用程序中,还以通过线程的异步特性来更好地处理事件;在应用服务器程序中可以通过建立多个线程来处理客户端的请求。线程甚至还可以简化虚拟机的实现,如Java虚拟机(JVM)的垃圾回收器(garbage collector)通常运行在一个或多个线程中。因此,使用线程将会从以下五个方面来改善我们的应用程序:
1. 充分利用CPU资源
现在世界上大多数计算机只有一块CPU。因此,充分利用CPU资源显得尤为重要。当执行单线程程序时,由于在程序发生阻塞时CPU可能会处于空闲状态。这将造成大量的计算资源的浪费。而在程序中使用多线程可以在某一个线程处于休眠或阻塞时,而CPU又恰好处于空闲状态时来运行其他的线程。这样CPU就很难有空闲的时候。因此,CPU资源就得到了充分地利用。
2. 简化编程模型
如果程序只完成一项任务,那只要写一个单线程的程序,并且按着执行这个任务的步骤编写代码即可。但要完成多项任务,如果还使用单线程的话,那就得在在程序中判断每项任务是否应该执行以及什么时候执行。如显示一个时钟的时、分、秒三个指针。使用单线程就得在循环中逐一判断这三个指针的转动时间和角度。如果使用三个线程分另来处理这三个指针的显示,那么对于每个线程来说就是指行一个单独的任务。这样有助于开发人员对程序的理解和维护。
3. 简化异步事件的处理
当一个服务器应用程序在接收不同的客户端连接时最简单地处理方法就是为每一个客户端连接建立一个线程。然后监听线程仍然负责监听来自客户端的请求。如果这种应用程序采用单线程来处理,当监听线程接收到一个客户端请求后,开始读取客户端发来的数据,在读完数据后,read方法处于阻塞状态,也就是说,这个线程将无法再监听客户端请求了。而要想在单线程中处理多个客户端请求,就必须使用非阻塞的Socket连接和异步I/O。但使用异步I/O方式比使用同步I/O更难以控制,也更容易出错。因此,使用多线程和同步I/O可以更容易地处理类似于多请求的异步事件。
4. 使GUI更有效率
使用单线程来处理GUI事件时,必须使用循环来对随时可能发生的GUI事件进行扫描,在循环内部除了扫描GUI事件外,还得来执行其他的程序代码。如果这些代码太长,那么GUI事件就会被“冻结”,直到这些代码被执行完为止。
在现代的GUI框架(如SWING、AWT和SWT)中都使用了一个单独的事件分派线程(event dispatch thread,EDT)来对GUI事件进行扫描。当我们按下一个按钮时,按钮的单击事件函数会在这个事件分派线程中被调用。由于EDT的任务只是对GUI事件进行扫描,因此,这种方式对事件的反映是非常快的。
5. 节约成本
提高程序的执行效率一般有三种方法:
(1)增加计算机的CPU个数。
(2)为一个程序启动多个进程
(3)在程序中使用多进程。
第一种方法是最容易做到的,但同时也是最昂贵的。这种方法不需要修改程序,从理论上说,任何程序都可以使用这种方法来提高执行效率。第二种方法虽然不用购买新的硬件,但这种方式不容易共享数据,如果这个程序要完成的任务需要必须要共享数据的话,这种方式就不太方便,而且启动多个线程会消耗大量的系统资源。第三种方法恰好弥补了第一种方法的缺点,而又继承了它们的优点。也就是说,既不需要购买CPU,也不会因为启太多的线程而占用大量的系统资源(在默认情况下,一个线程所占的内存空间要远比一个进程所占的内存空间小得多),并且多线程可以模拟多块CPU的运行方式,因此,使用多线程是提高程序执行效率的最廉价的方式。
三、Java的线程模型
由于Java是纯面向对象语言,因此,Java的线程模型也是面向对象的。Java通过Thread类将线程所必须的功能都封装了起来。要想建立一个线程,必须要有一个线程执行函数,这个线程执行函数对应Thread类的run方法。Thread类还有一个start方法,这个方法负责建立线程,相当于调用Windows的建立线程函数CreateThread。当调用start方法后,如果线程建立成功,并自动调用Thread类的run方法。因此,任何继承Thread的Java类都可以通过Thread类的start方法来建立线程。如果想运行自己的线程执行函数,那就要覆盖Thread类的run方法。
在Java的线程模型中除了Thread类,还有一个标识某个Java类是否可作为线程类的接口Runnable,这个接口只有一个抽象方法run,也就是Java线程模型的线程执行函数。因此,一个线程类的唯一标准就是这个类是否实现了Runnable接口的run方法,也就是说,拥有线程执行函数的类就是线程类。
从上面可以看出,在Java中建立线程有两种方法,一种是继承Thread类,另一种是实现Runnable接口,并通过Thread和实现Runnable的类来建立线程,其实这两种方法从本质上说是一种方法,即都是通过Thread类来建立线程,并运行run方法的。但它们的大区别是通过继承Thread类来建立线程,虽然在实现起来更容易,但由于Java不支持多继承,因此,这个线程类如果继承了Thread,就不能再继承其他的类了,因此,Java线程模型提供了通过实现Runnable接口的方法来建立线程,这样线程类可以在必要的时候继承和业务有关的类,而不是Thread类。
每天早上要去搭公司的厂车,大致厂车8:03分经过XX公交站,我们每天在此等车,为了不迟到,错过厂车我每天都比8点就会到站点等,以我的性格错过厂车是比较少。最近在想自己要做个什么小应用,后来就想到等厂车的应用。
思路如下:最初是想在厂车上装一下gps,让厂车把它的行车路线上报到服务器,我在手机客户端去查看,同时这个应用支持计算出厂车多久会到,因为自己手机上的gps模块也会把自己所以的位置上报到服务器,厂车是否是错过了,还是没有来。
推倒厂车上安装gps模块的想法,不实际,后改成坐在厂车上的同事上的手机,由它来上报车子的位置。
思路的推广,那如果是两个手机这样的一个应用,那么就可以变成双方碰面的应用了,应用场景就是,两个朋友约在XX地点见面,过去有种情况就是由于描述不清,两人相互寻找,最后碰面。那么现在就变成两个手机就像两个磁场,在应用上显示双方的位置,中间有系统指示的路线,双方不断的接近。
引用最近常见的oppo手机的广告,应用名叫:FIND ME,应用的本质就是分享自己所在的位置。
再思考,在明确自己想的东西后,上网查了一下这方面的应用,后来查询到此文:
http://soft.shouji.com.cn/news/560.shtml
原来手机QQ上有这方面的应用,这说明一点,在自己认为不错的创意的时候,说不定已经有前人已经想过,你不是第一个,第二,从上面描述上看,大家还是对这个创意表支持的态度,认为是挺实用的功能【有前景】。第三:目前的主要问题是功能做的不完善【备注:以腾讯的实力,以后一定会做的不错】。
如果把它当成单独的应用来做,我想有个问题,就是自己安装,对方也要安装,这是个问题,你无法保证对方也有安装,只有像QQ这样的应用,它的普遍性比较高。还有就是如何只是解决两人见面的问题,应用的价值也不是特高,除非深挖它的应用场景。需要再继续思考。
在此描述写我思考的过程,思考的过程也是有价值的。
感谢一楼。。
确实做事做久了,就算有新的想法,也会把一些实现的手段和方式硬生生的摁死在自己力所能及的范围里,而跳不出去。