论文部分内容阅读
摘要:多线程技术是提高程序并发性和效率的重要手段,在网络应用、数据库应用软件、因特网以及嵌入式系统的开发中得到广泛的应用。深入分析多线程及其同步机制、死锁问题,并将多线程技术运用到Java ME开发中的图像平滑移动、低层界面设计、无线消息接收、竞技游戏、移动电子商务开发实践中,并分析开发中会出现的死锁问题,对开发高效率的Java ME应用程序具有重要意义。
关键词:线程;同步;死锁;Java ME
中图分类号:TP316文献标识码:A文章编号:1009-3044(2008)35-2525-03
Research on Multithreading technology and its applications in Java ME
XU Jin-bao
(College of Computer Engineering, Nanjing Institute of Technology, Nanjing 211167, China)
Abstract: Multi-thread technology is the important means to improve the efficiency and concurrency of software system,widely used in the network applications, database applications, Internet and the the embedded systems. By deeply analynizing on multi-thread and its synchronization mechanisms, deadlock problems, multi-thread technology is discussed in the development of the image smoothing mobile, low-level interface design, wireless messages reception, fighting games, mobile e-commerce in Java ME, and a solution on the deadlock issues is provided. Multi-thread is of great significance to the development of highly efficient Java ME applications.
Key words: thread; synchronized; deadlock; Java ME
1 引言
多线程技术在网络应用、数据库应用软件、因特网以及嵌入式系统的开发中得到广泛的应用。多线程是提高系统效率、提高软件并发性的重要方式。传统的多进程、多线程理论对指导多线程编程有很大帮助,但跟实际应用结合的不够。在开发实践中,由于采用了多线程技术,就会带来一些与应用有关的问题,对这些问题处理的不好,可能会发生错误,或发生死锁,或系统效率得不到应有的提高。因此,对Java中多线程程序设计进行深入分析,并与Java ME开发结合起来,开发出效率高、运行稳定的手机应用软件。
2 多线程概念
传统操作系统的进程定义是:进程是程序在某个数据集上的一次执行,是资源分配与保护的单位,也是执行的单位。为了提高程序并发执行时的时空开销,使得并发粒度更小,并发性更好,现代操作系统就将进程的资源分配与保护功能和执行功能分开,执行功能由线程完成,进程仍然作为资源的分配与保护单位,无需频繁切换。线程共享进程地址空间,线程间通信简单。
线程具有运行、就绪和等待态。当多个线程对共享资源(临界资源)进行存取时,如果不采取有效的机制,会发生与时间有关的错误。对共享资源进行访问的线程中的代码段称为临界区(Critical Section),多个线程访问临界资源时,临界区管理必须满足三个条件:互斥使用,有空让进;忙则等待,有限等待;择一而入,算法可行。这是线程互斥的例子。线程还有同步的关系。当多个线程协作完成任务,如生产者/消费者问题,这就是线程的同步。互斥是线程的竞争关系,同步是线程的协作关系。互斥可以看作是一种特殊的同步。
此外,线程不断推进,会出现这样的一种状态:在一个线程集合中,所有的线程都处于等待态,等待的事件只能由此集合中的其他线程才能引发,这种局面就是死锁。
在软件开发实践中,对于多线程程序设计,要精益求精,慎之又慎,要妥善处理进程的同步与互斥关系,同时要避免死锁的发生。
3 Java中多线程的实现
3.1 Java多线程的两种实现方式
Java中的定时器就是一种多线程技术。首先需要定义一个继承自类TimerTask的类,然后建立一个Timer对象,将TimerTask类的对象加入到Timer的schedule()方法中即可,schedule()方法的第二个参数是定时时间间隔,真正需要并发执行的代码放在TimerTask的run()方法中。
在Java中有两种建立多线程程序的方法。一种是继承Thread类,一种是实现Runnable 接口。继承Thread类的方法简单,实现Runnable接口的方法更通用。需要并发执行的代码,两种方法都与定时器一样,都是在run()方法中予以实现。建立线程的两种方法如下:
classMyThread1 extends Thread{//继承Thread类的方式
public void run();
}
classMyThread2 implementss Runnable{//实现Runnable接口的方式
public void run();
}
在使用时,第一种方式在主方法里可以用new MyThread1().start();进行;第二种方式则稍微复杂一点:new Thread(new MyThread2()).start();。
3.2 Java中对线程同步与互斥的解决
在操作系统理论中,软件大师Dijkstra运用PV操作和信号量对进程的互斥和同步进行了有效的解决,该思想已经广泛应用到Java程序设计中。
在Java 中,解决线程的同步与互斥问题,可以通过Object中的三个方法:wait()、notify()和notifyAll(),加上关键字synchronized来解决。synchronized关键字有两种用法,一种是修饰对象中的方法,这样,该方法只能顺序执行,不可并发,从而保证线程之间的互斥;但是当方法代码不是自己写的或不能看到源代码,可以通过synchronized关键字来修饰该对象本身,这是可行的,但缺点是该对象中的所有的方法都被串行化了,虽然有些方法还是可以并发执行的,某种程度上降低了一些并发效率。
生产者/消费者问题是典型的线程同步与互斥问题。解决方法是:建立一个缓冲区类Box,有一整型属性value和一布尔变量available,该类有两个方法存put()和取get(),均为synchronized方法。当取数时,先检测available,若为false,则调用wait()进入等待态;若为true就直接取数,将available置为false,同时还需要调用notify()唤醒等待线程;当存数时,先检测available,若为true,则调用wait(),若为false,则存数,将available置为true,调用notify()唤醒等待线程。
缓冲区Box类设计好之后,就可以设计生产者Producer和消费者Consumer类。这两个类相对就简单了,这两个类都是线程类,有两个属性,一个是String类型的name,用来标识线程名词,另一个就是缓冲区Box类型的box,在run()方法中就可以通过Box的get()或put()方法进行生产或消费数据了。
此外,Java中还有一个线程局部变量ThreadLocal。如果把synchronized看着同步锁,是一种“以时间换空间”的策略,则ThreadLocal为每个独立的线程提供一个变量的副本,每个线程独立使用,每个线程修改线程变量时,实际上修改的是变量的副本,不会影响到其他线程,运用ThreadLocal实现线程的同步是一种“以空间换时间”的策略。
3.3 Java中对线程死锁的解决
破坏产生死锁的四个必要条件之一就能防止死锁,运用银行家算法能避免死锁,但这两种方法,前者条件太强,后者需要的系统开销太大,因此,在Java应用开发中都不现实。对死锁问题的解决主要是通过认真分析wait()方法调用的顺序,wait()方法调用顺序的不当,是产生死锁的主要原因。但值得注意的是,wait()调用次序的改变可能会降低程序运行的速度。由于死锁是小概率事件,有时宁可放松对死锁的监管,而让系统运行得更快。
3.4 Java中多线程设计的注意点
随着Java的发展,一些线程的方法已经过时(deprecated)。判断一个进程是否结束的iaAlive()已经不建议使用。判断线程是否结束可以通过建立一个线程状态的标志变量进行,等待其他线程结束可以使用join()方法。
此外,Thread中的stop()、suspend()和resume()方法,在实际编程中最好不要使用,这些方法可能会导致系统崩溃。
4 多线程在Java ME中的应用
4.1 多线程在图像平滑移动中的应用
在Java ME开发中,特别是在使用低层界面API时,经常需要在屏幕上移动一个物体或图像,并且移动到下一位置时需要进行复杂的坐标运算,如果把坐标计算与图像显示放在同一个线程中,则会出现图像显示慢,从而图像移动不平滑的现象。解决此问题,一般的方法是通过设计两个线程,一个线程专门负责图像显示,另一个线程专门负责坐标的计算,这两个线程的关系应该是制约的(同步的),在图像显示时,是不能改变坐标的。在Java ME环境下,对这两个线程的协调主要有两种解决办法。
4.1.1 应用关键字synchronized实现线程同步
计算坐标的工作可以设计成一个线程,为了简单起见,可以通过定时器来实现该线程。设计算坐标的方法为CalculateXY(),该方法必须是synchronized方法,将该方法调用放到TimerTask对象的run()方法中去,在调用了CalculateXY()方法计算出新的坐标后,需要调用repaint()方法进行屏幕重画。然后将该TimerTask对象作为new Timer()的第一个参数,第二参数和第三参数则可以设定为以毫秒计的定时间隔。
对于Canvas类中的paint()方法,在绘制图像时需要调用CalculateXY()方法计算出的坐标时,需要在执行过程中锁定Canvas对象,这样可以使别的方法不能存取Canvas对象中的图像对象,从而实现线程间的同步,主要代码如下:
protected void paint(Graphics g){
//一些背景设置
synchronized(this){
//图像显示
}
}
4.1.2 应用Display的callSerially方法实现线程同步
Display类中callSerially()方法是一个事件序列化的方法,它的作用是使指定的线程对象首先暂停,让系统首先完成屏幕的显示,等屏幕显示完毕以后再使线程继续执行。callSerially()方法只有一个类型为Runnable的参数,使用callSerially()方法引发的run()方法要在所有未处理的repaint请求都得到满足之后才被调用,如果将坐标计算方法CalculateXY()放入run()方法,则方法CalculateXY()就会与方法paint()串行执行,从而达到图像绘制与坐标计算的同步。具体实现方法如下:
Thread myThread=new Thread{//创建线程myThread
public void run(){
if(isRunning){//若线程未结束则运行,isRunning是线程结束标志变量
CalculateXY();//坐标计算
repaint();//屏幕重绘
serviceRepaints();//让paint()方法尽快执行
}
try{
Thread.sleep(50);
}
catch(Exception e){}
}
display.callSerially(this);//将线程myMyThread的run()方法加入到事件 //队列中,要求再次执行myThread的run()方法
};
myThread.start();//启动线程myThread,运行run()
//当paint()方法结束后,会再次启动run()方法运行,然后继续进行坐标计算,重绘。
4.2 多线程在竞技游戏中的应用
竞技游戏是Java ME的一项重要应用。竞技游戏设计中,需要使用LayerMananger对图层Layer进行管理,并且灵活使用观察窗口(ViewWindow)。背景采用TiledLayer,角色采用Sprite。竞技游戏需要很快地响应游戏用户的按键。一种有效的做法是:将获得按键以及按键的处理以及设定观察窗口放在一个线程中实现,这样就可以快速响应游戏用户的按键。
4.3 多线程在移动电子商务中的应用
移动电子商务需要有服务器,服务器必须能够同时处理多个客户的请求。服务端的软件设计必须是多线程的,可以采用线程池来进行。在高性能的服务器软件设计中,线程池的管理显得尤为重要,是影响性能的一个重要因素。
4.4 多线程在无线消息传递中的应用
在无线消息传递中,接收方为了接收信息,需要始终启动接收程序,在实际应用中,都是通过注册一个MessageListener接口来监听短消息的到来。为了能够及时处理新到达的短消息,对新消息的读取采用一个新的线程。更进一步,考虑到手机内存容量有限,CPU速度较慢的情况下,甚至还可以采用PushRegistry技术来更进一步提高效率,即由AMS(应用程序管理系统)监测短消息的到来,到有消息到来时,启动相应程序进行消息读取处理。
4.5 多线程在界面设计时会出现死锁的情况
与swing不同,MIDP中的所有界面控件都是线程安全的。 用户使用界面控件,在内部都是通过回调(callback)函数,而这些回调函数都进行了序列化,均是顺序执行的。但是,在低级界面设计中,假如在Canvas的serviceRepaints()方法中锁定了paint()方法将要访问的对象,就会发生死锁。这是因为serviceRepaints()是等待paint()方法立即执行,而paint()方法所访问的对象却被锁定了,这样两个方法都不能完成,从而互相等待。因此,在serviceRepaints()方法中不能锁定paint()方法将要访问的对象。
5 结束语
以上对多线程技术及其在Java ME中的应用进行了分析,但多线程技术决不仅仅应用在以上这些方面。随着Java技术的发展,Java的多线程技术也在不断发展与成熟,在Java ME中的应用将会更加深入。对Java多线程的深入掌握和应用,将会开发出更加优秀的移动软件系统。
参考文献:
[1] 孙钟秀.操作系统[M].北京:高等教育出版社,2008:164-217.
[2] 詹建飞.J2ME开发精解[M].北京:电子工业出版社,2006:155-171.
[3] 庄东.JBuilderX无线应用开发[M].北京:电子工业出版社,2004:160-171.
[4] 孔明放.J2ME程序设计教程[M].北京:科学出版社,2005:142-147.
[5] 池雅庆,周珺.J2ME手机应用项目开发实践[M].北京:中国铁道出版社,2007:130-145.
关键词:线程;同步;死锁;Java ME
中图分类号:TP316文献标识码:A文章编号:1009-3044(2008)35-2525-03
Research on Multithreading technology and its applications in Java ME
XU Jin-bao
(College of Computer Engineering, Nanjing Institute of Technology, Nanjing 211167, China)
Abstract: Multi-thread technology is the important means to improve the efficiency and concurrency of software system,widely used in the network applications, database applications, Internet and the the embedded systems. By deeply analynizing on multi-thread and its synchronization mechanisms, deadlock problems, multi-thread technology is discussed in the development of the image smoothing mobile, low-level interface design, wireless messages reception, fighting games, mobile e-commerce in Java ME, and a solution on the deadlock issues is provided. Multi-thread is of great significance to the development of highly efficient Java ME applications.
Key words: thread; synchronized; deadlock; Java ME
1 引言
多线程技术在网络应用、数据库应用软件、因特网以及嵌入式系统的开发中得到广泛的应用。多线程是提高系统效率、提高软件并发性的重要方式。传统的多进程、多线程理论对指导多线程编程有很大帮助,但跟实际应用结合的不够。在开发实践中,由于采用了多线程技术,就会带来一些与应用有关的问题,对这些问题处理的不好,可能会发生错误,或发生死锁,或系统效率得不到应有的提高。因此,对Java中多线程程序设计进行深入分析,并与Java ME开发结合起来,开发出效率高、运行稳定的手机应用软件。
2 多线程概念
传统操作系统的进程定义是:进程是程序在某个数据集上的一次执行,是资源分配与保护的单位,也是执行的单位。为了提高程序并发执行时的时空开销,使得并发粒度更小,并发性更好,现代操作系统就将进程的资源分配与保护功能和执行功能分开,执行功能由线程完成,进程仍然作为资源的分配与保护单位,无需频繁切换。线程共享进程地址空间,线程间通信简单。
线程具有运行、就绪和等待态。当多个线程对共享资源(临界资源)进行存取时,如果不采取有效的机制,会发生与时间有关的错误。对共享资源进行访问的线程中的代码段称为临界区(Critical Section),多个线程访问临界资源时,临界区管理必须满足三个条件:互斥使用,有空让进;忙则等待,有限等待;择一而入,算法可行。这是线程互斥的例子。线程还有同步的关系。当多个线程协作完成任务,如生产者/消费者问题,这就是线程的同步。互斥是线程的竞争关系,同步是线程的协作关系。互斥可以看作是一种特殊的同步。
此外,线程不断推进,会出现这样的一种状态:在一个线程集合中,所有的线程都处于等待态,等待的事件只能由此集合中的其他线程才能引发,这种局面就是死锁。
在软件开发实践中,对于多线程程序设计,要精益求精,慎之又慎,要妥善处理进程的同步与互斥关系,同时要避免死锁的发生。
3 Java中多线程的实现
3.1 Java多线程的两种实现方式
Java中的定时器就是一种多线程技术。首先需要定义一个继承自类TimerTask的类,然后建立一个Timer对象,将TimerTask类的对象加入到Timer的schedule()方法中即可,schedule()方法的第二个参数是定时时间间隔,真正需要并发执行的代码放在TimerTask的run()方法中。
在Java中有两种建立多线程程序的方法。一种是继承Thread类,一种是实现Runnable 接口。继承Thread类的方法简单,实现Runnable接口的方法更通用。需要并发执行的代码,两种方法都与定时器一样,都是在run()方法中予以实现。建立线程的两种方法如下:
classMyThread1 extends Thread{//继承Thread类的方式
public void run();
}
classMyThread2 implementss Runnable{//实现Runnable接口的方式
public void run();
}
在使用时,第一种方式在主方法里可以用new MyThread1().start();进行;第二种方式则稍微复杂一点:new Thread(new MyThread2()).start();。
3.2 Java中对线程同步与互斥的解决
在操作系统理论中,软件大师Dijkstra运用PV操作和信号量对进程的互斥和同步进行了有效的解决,该思想已经广泛应用到Java程序设计中。
在Java 中,解决线程的同步与互斥问题,可以通过Object中的三个方法:wait()、notify()和notifyAll(),加上关键字synchronized来解决。synchronized关键字有两种用法,一种是修饰对象中的方法,这样,该方法只能顺序执行,不可并发,从而保证线程之间的互斥;但是当方法代码不是自己写的或不能看到源代码,可以通过synchronized关键字来修饰该对象本身,这是可行的,但缺点是该对象中的所有的方法都被串行化了,虽然有些方法还是可以并发执行的,某种程度上降低了一些并发效率。
生产者/消费者问题是典型的线程同步与互斥问题。解决方法是:建立一个缓冲区类Box,有一整型属性value和一布尔变量available,该类有两个方法存put()和取get(),均为synchronized方法。当取数时,先检测available,若为false,则调用wait()进入等待态;若为true就直接取数,将available置为false,同时还需要调用notify()唤醒等待线程;当存数时,先检测available,若为true,则调用wait(),若为false,则存数,将available置为true,调用notify()唤醒等待线程。
缓冲区Box类设计好之后,就可以设计生产者Producer和消费者Consumer类。这两个类相对就简单了,这两个类都是线程类,有两个属性,一个是String类型的name,用来标识线程名词,另一个就是缓冲区Box类型的box,在run()方法中就可以通过Box的get()或put()方法进行生产或消费数据了。
此外,Java中还有一个线程局部变量ThreadLocal。如果把synchronized看着同步锁,是一种“以时间换空间”的策略,则ThreadLocal为每个独立的线程提供一个变量的副本,每个线程独立使用,每个线程修改线程变量时,实际上修改的是变量的副本,不会影响到其他线程,运用ThreadLocal实现线程的同步是一种“以空间换时间”的策略。
3.3 Java中对线程死锁的解决
破坏产生死锁的四个必要条件之一就能防止死锁,运用银行家算法能避免死锁,但这两种方法,前者条件太强,后者需要的系统开销太大,因此,在Java应用开发中都不现实。对死锁问题的解决主要是通过认真分析wait()方法调用的顺序,wait()方法调用顺序的不当,是产生死锁的主要原因。但值得注意的是,wait()调用次序的改变可能会降低程序运行的速度。由于死锁是小概率事件,有时宁可放松对死锁的监管,而让系统运行得更快。
3.4 Java中多线程设计的注意点
随着Java的发展,一些线程的方法已经过时(deprecated)。判断一个进程是否结束的iaAlive()已经不建议使用。判断线程是否结束可以通过建立一个线程状态的标志变量进行,等待其他线程结束可以使用join()方法。
此外,Thread中的stop()、suspend()和resume()方法,在实际编程中最好不要使用,这些方法可能会导致系统崩溃。
4 多线程在Java ME中的应用
4.1 多线程在图像平滑移动中的应用
在Java ME开发中,特别是在使用低层界面API时,经常需要在屏幕上移动一个物体或图像,并且移动到下一位置时需要进行复杂的坐标运算,如果把坐标计算与图像显示放在同一个线程中,则会出现图像显示慢,从而图像移动不平滑的现象。解决此问题,一般的方法是通过设计两个线程,一个线程专门负责图像显示,另一个线程专门负责坐标的计算,这两个线程的关系应该是制约的(同步的),在图像显示时,是不能改变坐标的。在Java ME环境下,对这两个线程的协调主要有两种解决办法。
4.1.1 应用关键字synchronized实现线程同步
计算坐标的工作可以设计成一个线程,为了简单起见,可以通过定时器来实现该线程。设计算坐标的方法为CalculateXY(),该方法必须是synchronized方法,将该方法调用放到TimerTask对象的run()方法中去,在调用了CalculateXY()方法计算出新的坐标后,需要调用repaint()方法进行屏幕重画。然后将该TimerTask对象作为new Timer()的第一个参数,第二参数和第三参数则可以设定为以毫秒计的定时间隔。
对于Canvas类中的paint()方法,在绘制图像时需要调用CalculateXY()方法计算出的坐标时,需要在执行过程中锁定Canvas对象,这样可以使别的方法不能存取Canvas对象中的图像对象,从而实现线程间的同步,主要代码如下:
protected void paint(Graphics g){
//一些背景设置
synchronized(this){
//图像显示
}
}
4.1.2 应用Display的callSerially方法实现线程同步
Display类中callSerially()方法是一个事件序列化的方法,它的作用是使指定的线程对象首先暂停,让系统首先完成屏幕的显示,等屏幕显示完毕以后再使线程继续执行。callSerially()方法只有一个类型为Runnable的参数,使用callSerially()方法引发的run()方法要在所有未处理的repaint请求都得到满足之后才被调用,如果将坐标计算方法CalculateXY()放入run()方法,则方法CalculateXY()就会与方法paint()串行执行,从而达到图像绘制与坐标计算的同步。具体实现方法如下:
Thread myThread=new Thread{//创建线程myThread
public void run(){
if(isRunning){//若线程未结束则运行,isRunning是线程结束标志变量
CalculateXY();//坐标计算
repaint();//屏幕重绘
serviceRepaints();//让paint()方法尽快执行
}
try{
Thread.sleep(50);
}
catch(Exception e){}
}
display.callSerially(this);//将线程myMyThread的run()方法加入到事件 //队列中,要求再次执行myThread的run()方法
};
myThread.start();//启动线程myThread,运行run()
//当paint()方法结束后,会再次启动run()方法运行,然后继续进行坐标计算,重绘。
4.2 多线程在竞技游戏中的应用
竞技游戏是Java ME的一项重要应用。竞技游戏设计中,需要使用LayerMananger对图层Layer进行管理,并且灵活使用观察窗口(ViewWindow)。背景采用TiledLayer,角色采用Sprite。竞技游戏需要很快地响应游戏用户的按键。一种有效的做法是:将获得按键以及按键的处理以及设定观察窗口放在一个线程中实现,这样就可以快速响应游戏用户的按键。
4.3 多线程在移动电子商务中的应用
移动电子商务需要有服务器,服务器必须能够同时处理多个客户的请求。服务端的软件设计必须是多线程的,可以采用线程池来进行。在高性能的服务器软件设计中,线程池的管理显得尤为重要,是影响性能的一个重要因素。
4.4 多线程在无线消息传递中的应用
在无线消息传递中,接收方为了接收信息,需要始终启动接收程序,在实际应用中,都是通过注册一个MessageListener接口来监听短消息的到来。为了能够及时处理新到达的短消息,对新消息的读取采用一个新的线程。更进一步,考虑到手机内存容量有限,CPU速度较慢的情况下,甚至还可以采用PushRegistry技术来更进一步提高效率,即由AMS(应用程序管理系统)监测短消息的到来,到有消息到来时,启动相应程序进行消息读取处理。
4.5 多线程在界面设计时会出现死锁的情况
与swing不同,MIDP中的所有界面控件都是线程安全的。 用户使用界面控件,在内部都是通过回调(callback)函数,而这些回调函数都进行了序列化,均是顺序执行的。但是,在低级界面设计中,假如在Canvas的serviceRepaints()方法中锁定了paint()方法将要访问的对象,就会发生死锁。这是因为serviceRepaints()是等待paint()方法立即执行,而paint()方法所访问的对象却被锁定了,这样两个方法都不能完成,从而互相等待。因此,在serviceRepaints()方法中不能锁定paint()方法将要访问的对象。
5 结束语
以上对多线程技术及其在Java ME中的应用进行了分析,但多线程技术决不仅仅应用在以上这些方面。随着Java技术的发展,Java的多线程技术也在不断发展与成熟,在Java ME中的应用将会更加深入。对Java多线程的深入掌握和应用,将会开发出更加优秀的移动软件系统。
参考文献:
[1] 孙钟秀.操作系统[M].北京:高等教育出版社,2008:164-217.
[2] 詹建飞.J2ME开发精解[M].北京:电子工业出版社,2006:155-171.
[3] 庄东.JBuilderX无线应用开发[M].北京:电子工业出版社,2004:160-171.
[4] 孔明放.J2ME程序设计教程[M].北京:科学出版社,2005:142-147.
[5] 池雅庆,周珺.J2ME手机应用项目开发实践[M].北京:中国铁道出版社,2007:130-145.