——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
一 线程概述
1 进程
一个进程是一个包括有自身执行地址的程序,在多任务操作系统中,可以分配CPU时间给每一个进程。CPU在指定时间片段内执行某个进程,然后在下一个时间片段跳至另一个进程中执行,由于转换速度很快,使得每个进程像是在同时进行。
通常将正在运行的程序称为进程,现在的计算机基本上都支持多进程操作,比如使用计算机时可以边上网边听音乐,而计算机上只有一块CPU。实际上,这些进程并不是同时运行的,而只是CPU在利用不同时间片段去交替执行每个进程,时间到了,轮换下一个进程。多进程执行过程如图所示。
图 多进程执行结构图
2 线程
在一个进程内部也可以执行多任务,可以将进程内部的任务称为线程,线程是进程中的实体,一个进程可以拥有多个线程。多线程的执行过程如图所示。
图 多线程执行结构图
一个线程是进程内的一个单一的顺序控制流程。通常所说的多线程,指的是一个进程可以同时运行几个任务,每个任务由一个线程来完成。也就是说,多个线程可以同时运行,并且在一个进程内执行不同的任务。
一个线程是进程中执行的一个流程,一个程序中同时可能进行多个不同的子流程,每一个子流程可以得到一小段程序执行时间,每执行完一个流程跳到下一个流程,这里所说的程序流程也就是线程。由于转换速度较快,就像是一个程序同时在处理多个任务一样。
线程必须拥有父进程,系统没有为线程分配资源,它与进程中的其他线程共享该进程的系统资源。如果一个进程中的多个线程共享相同的内存地址空间,这就意味着这些线程可以访问相同的变量和对象,这让线程之间共享信息变得更容易。
所谓的单线程是一个程序内某个结构化的流程控制,有时候被称做“执行环境”或“轻量级程序”,它是由上而下的结构化程序,例如,main()函数是Java程序执行的开始点,而程序的中间区域是一串连续的执行过程,程序的结束点则是程序最后的“}”符号。
可以将一个结构化“程序”看做是一个线程,它也是由一个开始点、一串连续的执行过程及一个结束点组成,虽然线程就如同是一个真正的“程序”,但实际上它只是在一个完整程序下的某个执行流程,需要运用系统配置给该程序的资源及该程序的环境来执行,所以,它又被称为“轻量级的程序”。因此,一个多线程的Java程序,即使它在执行期间能够同时由多个“线程”进行不同的工作,但对于操作系统而言,仍然只当成是一个“程序”在运作,其实是CPU不断地在做转换(转换执行控制权)的工作而形成了多个线程同时运作的假象。多线程程序结构如图所示。
图 多线程程序结构图
二 线程的创建
1 继承Thread类
Thread类是java.lang包中的一个类,从这个类中实例化的对象代表线程,程序员启动一个新线程需要建立Thread实例。Thread类中常用的两个构造方法如下:
public Thread(String threadName);
public Thread();
其中,第一个构造方法是创建一个名称为threadName的线程对象。
继承Thread类创建一个新的线程的语法如下:
public class ThreadTest extends Thread{
//...
}
完成线程真正功能的代码放在类的run()方法中,当一个类继承Thread类后,就可以在该类中覆盖run()方法,将实现该线程功能的代码写入run()方法中,然后同时调用Thread类中的start()方法执行线程,也就是调用run()方法。
Thread对象需要一个任务来执行,任务是指线程在启动时执行的工作,该工作的功能代码被写在run()方法中。这个run()方法必须使用如下这种语法格式:
public void run(){
//...
}
注意:
如果start()方法调用一个已经启动的线程,系统将抛出IllegalThreadState Exception异常。
当执行一个线程程序时,就自动产生一个线程,主方法正是在这个线程上运行的。当不再启动其他线程时,该程序就为单线程程序,比如在本章以前的程序都是单线程程序。主方法线程启动由Java虚拟机负责,程序员负责启动自己的线程。
语法如下:
public static void main(String[] args) {
new ThreadTest().start();
}
下面看一个继承Thread类的实例。
【例】 在项目中创建ThreadTest类,该类使用继承Thread类方法创建线程。
public class ThreadTest extends Thread{ //指定类继承Thread类
private int count=10;
public void run(){ //重写run()方法
while(true){
System.out.print(count+" "); //打印count变量
if(--count==0){ //使count变量自减,当自减为0时,退出循环
return; //退出程序
}
}
}
public static void main(String[] args) {
new ThreadTest().start(); //启动线程
}
}
在Eclipse中运行本实例,运行结果如图所示。
图 使用继承Thread类方法
在上述实例中,该类继承了Thread类,然后在该类中覆盖了run()方法。通常在run()方法中使用了无限循环的形式,使得线程一直运行下去,所以要指定一个跳出循环的条件,如本实例中使用变量count递减为零作为跳出循环的条件。 在main方法中,使线程执行需要调用Thread类中的start()方法,start()方法调用被覆盖的run()方法,如果不调用start()方法,线程永远都不会启动,在主方法没有调用start()方法之前,Thread对象只是一个实例,而不是一个真正的线程。
2 实现Runnable接口
到目前为止,线程都是通过扩展Thread类来创建的,如果程序员需要继承其他类(非Thread类)并使该程序可以使用线程,就需要使用Runnable接口。例如,一个扩展JFrame类的GUI程序不可能再继承Thread类,因为Java语言中不支持多继承,这时该类就需要实现Runnable接口使其具有使用线程的功能。
实现Runnable接口的语法如下:
public class Thread extends Object implements Runnable
说明:
有兴趣的读者可以查询API,从中可以惊奇地发现实质上Thread类就实现了Runnable接口,其中的run()方法正是对Runnable接口中的run()方法的具体实现。
实现Runnable接口的程序会创建一个Thread对象,并将Runnable对象与Thread对象相关联。Thread类中有两个构造方法,如下所示:
public Thread(Runnable r)
public Thread(Runnable r,String name)
这两个构造方法的参数中都存在Runnable实例,使用以上构造方法就可以将Runnable实例与Thread实例相关联。
使用Runnable接口启动新的线程的步骤如下:
1)建立Runnable对象。
2)使用参数为Runnable对象的构造方法创建Thread实例。
3)调用start()方法启动线程。
通过Runnable接口创建线程时,程序员首先需要编写一个实现Runnable接口的类,然后实例化该类的对象,这样就建立了Runnable对象,接下来使用相应的构造方法创建Thread实例,最后使用该实例调用Thread类中的start()方法启动线程。图表明了实现Runnable接口创建线程的流程。
图 实现Runnable接口创建线程的流程
线程最引人注目的部分应该是与Swing相结合创建GUI程序,下面演示一个GUI程序,该程序实现了图标滚动的功能。
【例】 在项目中创建ThreadDemo类,该类实现Runnable接口,在run()方法中输出信息,并在主方法中启动线程。
public class ThreadDemo implements Runnable {
@Override
public void run() {
for(int i = 0;i<10;i++){ //定义循环
System.out.println("TestThread 线程在运行"); //输出提示信息
}
}
public static void main(String[] args) {
ThreadDemo demo = new ThreadDemo(); //创建本类对象
new Thread(demo).start(); //创建线程对象,并启动线程
for(int i = 0;i<10;i++){ //定义循环
System.out.println("main 线程在运行"); //输出提示信息
}
}
}
运行本例,由于笔者的计算机为双核,因此结果如图所示,如果读者的计算机配置不同,可能会产生其他的效果。
图 实例的运行结果
3 两种创建线程方式的比较
实现Runnble接口的优缺点分别如下。
从面向对象的角度来看,Thread类是一个虚拟处理机严格的封装,因此只有当处理机模型修改或扩展时,才应该继承该类。
由于Java 技术只允许单一继承,所以如果已经继承了Thread类,就不能再继承其他任何类,这会使用户只能采用实现Runnable接口的方式创建线程。
继承Thread类的优缺点分别如下。
当一个run()方法体现在继承Thread的类中,用this指向实际控制运行的Thread实例,因此,代码不再需要使用控制“Thread.currenThread().sleep()”,而可以简单地使用“Thread.sleep()”。继承Thread类方式使代码变得简单易读。