Java多线程初学者指南(1):线程简介
副标题#e#
一、线程概述
线程是措施运行的根基执行单位。当操纵系统(不包罗单线程的操纵系统,如微软早期的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更有效率
#p#分页标题#e#
利用单线程来处理惩罚GUI事件时,必需利用轮回来对随时大概产生的GUI事件举办扫描,在轮回内部除了扫描GUI事件外,还得来执行其他的措施代码。假如这些代码太长,那么GUI事件就会被“冻结”,直到这些代码被执行完为止。
在现代的GUI框架(如SWING、AWT和SWT)中都利用了一个单独的事件分配线程(event dispatch thread,EDT)来对GUI事件举办扫描。当我们按下一个按钮时,按钮的单击事件函数会在这个事件分配线程中被挪用。由于EDT的任务只是对GUI事件举办扫描,因此,这种方法对事件的反应长短常快的。
#p#副标题#e#
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类。