[摘 要]本文就如何实现Java 的多线程、线程调度模式、同步互斥机制以及内置多线程功能进行了深入的探讨,并对线程的状态、创建和控制方法以及避免死锁的方法作了归纳总结,指出了线程实际应用领域以及在编程时应注意的事项。
[关键词]多线程 线程调度 同步机制 死锁
作者简介:闻丽华(1969-),女,大连水产学院职业技术学院 计算机系讲师 工程硕士学位。
引入线程是十分必要的,线程共享相同的地址空间并共同构成一个大的进程,所以同一进程中的线程间的通讯是非常简单而有效的,上下文切换非常快并且是整个大程序的一部分切换。线程仅是过程调用,它们彼此独立执行,线程使得在一个应用程序中同时使用多个线程来完成不同的任务,程序的编写更加自由和丰富,可以大大简化应用程序设计。如果要一程序中实现多段代码同时交替运行,就需要产生多个线程,并指定每个线程上所要运行的程序代码段,这就是多线程。多线程可以增进程序的交互性, 提供更好功能、更好的GUI 和更好的服务器功能。
Java 在两方面支持多线程,一方面,Java 环境本身就是多线程的,若干个系统线程运行负责必要的无用单元回收, 系统维护等系统级操作;另一方面,Java 语言内置多线程控制, 可以大大简化多线程应用程序开发。
一、Java 中如何实现多线程
Java 的多线程机制使得在一个程序里可同时执行多个任务。 只有在多CPU 的计算机或者在网络计算体系结构下,将Java 程序划分为多个并发执行线程后,同时启动多个线程运行,使不同的线程运行在基于不同处理器的Java虚拟机中,才能提高应用程序的执行效率。
(一)Java多线程实现方法
Java 采用两种途径实现多线程机制:一种是应用程序的并发运行对象直接继承Thread 类,另外一种是定义并发执行对象实现Runnable 接口。
1.用Thread 类创建线程
Java的线程是通过java.lang.Thread类来控制的,一个Thread类的对象代表一个线程。当编写Thread 类的子类时,在子类中重写父类的run()方法,该方法中包含了线程的操作,这样的程序需要建立自己的线程时,只需要创建一个已定义好的Thread 子类的实例就可以了,当创建的线程需要调用start()方法开始运行时,run()方法将被自动执行。
2.使用Runnable 接口创建多线程
通过实现Runnable 作为一个目标对象,用Runnable 目标对象初始化Thread 类,提供run()方法,实现的同时还可以继承其他类,可以避免由单继承的局限。几乎所有的线程都可以用Runnable 接口。当线程被构造时, 需要的代码和数据通过一个对象作为构造函数实参传递进去, 这个对象就是实现了Runnable接口的类的实例。
3.两种方法的对比分析
直接继承Thread 类的方法不能再从其他类继承,编写简单,可以直接操作线程;实现Runnable 接口的方法适合多个相同程序代码的线程去处理同一资源的情况,可以避免由于Java单继承特性带来的局限。两者的重要区别在于启动多线程对象的设计方法不同。在具体应用中,采用哪种方法来构造线程要视情况而定。事实上,几乎所有多线程应用都可用第二种方式,即实现Runnable接口。
(二)线程的状态控制
线程包括四个状态:new (开始),running (运行),wait (等候)和done(结束)。当线程被创建并还未运行时, 线程处于new 状态, 在这个状态下,线程不能运行。对于新创建的线程,调用start 方法之后, 会自动调用start 方法,这时线程进入就绪状态。在程序之间用某种方法把处理器的执行时间分成时间片, 位于就绪状态的每个线程都是能运行的,但在某一时刻, 系统处理器只能运行一个线程。由于某些原因, 线程可以被临时暂停进入等候状态。处于这种状态的线程,对于用户来讲仍然有效,仍然可以重新进入就绪状态。当线程因不再需要而进入结束状态时,线程就不能再被恢复和执行。
(三)线程的调度机制
为了控制线程的运行,Java定义了线程调度器来监控系统中处于就绪状态的所有线程。 线程调度器按照线程的优先级决定那个线程投入处理器运行,在多个线程处于就绪状态的条件下,具有高优先级的线程会在低优先级线程之前得到执行,线程调度器同样采用“抢占式”策略来调度线程执行,即当前线程执行过程中有较高优先级的线程进入就绪状态,则高优先级的线程立即被调度执行,具有相同优先级的所有线程采用轮转的方式来共同分配CPU 时间片。
线程调度的意义在于避免多个线程争用有限资源而导致应用系统死机或者崩溃。Java 支持抢占式调度,因此线程的优先级尤为重要,它是线程调度的决策依据。Java 中线程的优先级分为10 个等级,分别用1~10 之间的数字表示,数字越大表明线程的级别越高,线程创建时,子线程继承父线程的优先级。线程运行的顺序以及从处理器中获得的时间数量主要取决于开发者, 处理器给每个线程分配一个时间片, 而且线程的运行不能影响整个系统。处理器线程的系统或者是抢占式的, 或者是非抢占式的。抢占式系统在任何给定的时间内将运行最高优先级的线程, 系统中的所有线程都有自己的优先级。Thread 类提供了setPriority 和getPriority方法来重新设置和读取优先权。Java 虚拟机是抢占式的, 它能保证运行优先级最高的线程。
Java 通过创建了线程组管理成百上千个线程。线程组是线程的一个谱系组, 每个组包含的线程数不受限制,能对每个线程命名并能在整个线程组中执行(Suspend)和停止(Stop)这样的操作。
(四)多线程的同步机制
Java 应用程序的多个线程共享同一进程的数据资源,多个用户线程在并发运行过程中可能同时访问具有敏感性的内容,竞争共享资源。必须采用某种方法来确定资源在某一时刻仅被一个线程占用,达到此目的的过程叫同步(synchronized),在Java 中定义了线程同步的概念,实现对共享资源的一致性维护。多线程同步机制的实现是基于管程 (Monitor)机制。 管程是一个互斥独占锁定的对象,或称互斥体。 在给定的时间里,仅有一个线程可以获得管程。所有其他试图进入已经锁定的管程的线程必须挂起,当拥有管程的线程从同步方法中返回或异常退出时,其他线程才可以获得管程。可以用synchronized 和Object 类的方法wait ()、notify()和notifyAll()实现多线程同步。 线程同步有同步方法和同步语句两种: (1)同步方法。 在方法的声明中用synchronized 关键字修饰,可以对该方法中的所有代码实现同步,其格式为public synchronized void method (){...}. (2)同步语句。 用synchronized 关键字修饰的程序块称同步语句或同步块。 当需要调用某个类中的方法,而该类没有同步方法,但又必须实现同步时,就要采用同步语句。 只要将对这个类定义的方法的调用放入一个synchronized 块内就可以了,其格式为: synchronized (object){...},其中,object 是对同步对象的引用。从经过线程同步机制定义后的代码形式可以看出:在对共享资源进行访问的方法访问属性关键字(public)后附加同步定义关键字synchronized,使得同步方法在对共享资源访问的时候,为这些敏感资源附加共享锁来控制方法执行期间的资源独占性,实现了应用系统数据资源的一致性管理和维护。
(五)避免死锁
死锁是一个经典的多线程问题,它是一种少见的、而且难于调试的错误,在两个线程对两个同步对象具有循环依赖时,就会出现死锁。因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。死锁不是资源不够引起的,而是由线程的调度引起的,对于死锁可用下述方法解决: (1)尝试在尽可能短的时间内执行锁定的代码,占用时间越长,另一个线程出现和需要对象的可能性越大;(2)当你从另一个被同步的方法中激活被同步的方法时要小心,最好是清楚地定义每个线程的任务,并考虑使用什么数据和什么时间使用。
二、多线程的应用
在实际应用中,线程使用的范围很广,可用于控制实时数据处理、快速的网络服务,还有更快的图像绘制和打印,以及数据库中数据的取回和处理等等。 在Java 中有些线程在不停运行,提供一些基本服务,其中一个典型的例子就是垃圾收集线程( GarbageCollectionThread )。 该线程由Java虚拟机(JVM )提供,由一个中心线程控制执行,它扫描程序中不再被访问的变量,将其所占的系统资源释放给系统,当Java 线程请求存储时,如果Java 虚拟机不能分配足够的存储块来满足这个请求,则可以说出现了分配失败,这就不可避免要进行垃圾回收。结合Java 的多线程应用及其它特性,如平台无关性、对网络强有力的支持,特别是Sun 公司对Java 语言进行了扩充,引入了远程方法调用RMI 机制,它提供了在并行计算环境下应用Java 语言的一些基本功能。由此可见,Java 在分布式计算、并行计算方面将大有作为。
三、使用多线程应注意的问题
利用多线程的并发执行特点,无疑会加快程序的运行,提高CPU 、内存等系统资源的使用效率,但在应用多线程时,要注意同时运行在内存中的多个线程之间的关系。由于线程执行不受建立先后的约束,线程间共享数据可能会出现程序员想象不到的事情,还有多个线程同时访问修改同一个数据、资源竞争和死锁问题,都需要程序员周密的考虑。总体来说,在程序中使用多线程需要考虑以下几个问题:
(一)创建线程需要占用更多的内存资源;
(二)创建线程需要增加CPU 跟踪线程、切换线程的时间开销;
(三)多线程编程必须考虑资源共享问题、同时访问通讯端口问题以及系统资源竞争和死锁问题;
(四)同一任务下的多个线程同时访问和修改某个数据时,要考虑数据的安全问题。由此看出,线程不是越多越好,而是要适度,并且对多线程编程的程序员也提高了要求。总之,多线程会使软件编程变得更加灵活,同时也给程序员带来了更大的机遇和挑战。
四、结束语
由于Java 的多线程功能齐全, 它带来的好处也是显然易见的。在开发难易程度和性能上都比单线程要好。另一方面,由于多线程还没有充分利用基本操作系统的这一功能,对于不同的系统, 上面的程序可能会出现截然不同的结果, 这使编程者偶会感到迷惑不解。
实际运用时,要充分考虑多线程编程的复杂性以及线程切换开销带来的多线程程序的低效性,要注意是否需要多线程,就是要看这是否也是它的内在特点,只有当完全符合多线程的特点时,多线程机制对线程间通信和线程管理的强大支持才能有用武之地,这时使用多线程才是值得的。实际上, 无论是系统级还是语言级的多线程, 如果操作系统本身不支持多线程,Java 的多线程就可能只是受限的或不完全的多线程。
参考文献
[1]卢俊岭,师军,基于Java 的多线程机制[J]陕西师范大学学报(自然科学版) ,2000, (4)
[2]马红霞,张春芳,JAVA 与多线程程序设计[J]河北工业科技,2004, (4)
[3]王克宏,Java 技术教程(基础篇) [M] 北京:清华大学出版社,2005
[4]耿祥义,张跃平,Java 2 实用教程[M]北京:清华大学出版社,2006.8