1. Java中的线程
1.1 程序、进程与线程
程序是一段静态的代码,它是应用软件执行的蓝本。
进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。
线程是比进程更小的执行单位。一个进程在执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念。
1.2 线程的状态与生命周期
Java使用Thread类及其子类的对象来表示线程,新建的线程在它的一个完整的生命周期中通常要经历如下的4种状态。
l 新建
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它已经有了相应的内存空间和其他资源。
l 运行
线程创建之后就具备了运行的条件,一旦轮到它来享用CPU资源时,即JVM将CPU的使用权切换给该线程时,此线程就可以脱离创建它的主线程,独立开始自己的生命周期了。
l 中断
l 死亡
1.3 线程调度与优先级
2. Thread类的子类创建线程
3. 使用Runnable接口
使用Thread子类创建线程的优点是:可以在子类中增加新的成员变量,使线程具有某种属性,也可以在子类中新增加方法,使线程具有某种功能。但是Java不支持多继承,Thread类的子类不能再拓展其他的类。
1) Runnable接口与目标对象
创建线程的另一个途径就是用Thread类直接创建线程对象。使用Thread创建线程对象时,通常使用的构造方法是:
Thread(Runnabletarget)
该构造方法中的参数是一个Runnable类型的接口,因此,在创建线程对象时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称作所创线程的目标对象,当线程调用start()方法后,一旦轮到它来享用CPU资源,目标对象就会自动调用接口中的run()方法(接口回调),这一过程是自动实现的,用户程序只需要让线程调用start()方法即可,也就是说,当线程被调度并转入运行状态时,所执行的就是run()方法中所规定的操作。
2) 关于run()方法中的局部变量
对于具有相同目标对象的线程,当其中一个线程享用CPU资源时,目标对象自动调用接口中的run()方法,这时,run()方法中的局部变量被分配内存空间,当轮到另一个线程享用CPU资源时,目标对象会再次调用接口中的run()方法,那么,run()方法中的局部变量会再次分配内存空间。也就是说run()方法已经启动运行了两次,分别运行在不同的线程中,即运行在不同的时间片内。不同线程的run()方法中的局部变量互不干扰,一个线程改变了自己的run()方法中局部变量的值不会影响其他线程的run()方法中的局部变量的值。
3) 在线程中启动其他线程
线程通过调用start()方法将启动该线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的主线程独立开始自己的生命周期了。
4. 线程的常用方法
1) start()方法
线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
2) run()方法
Thread类的run()方法与Runnable()接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后执行的操作,都是系统自动调用而用户程序不得引用的方法。系统的Thread类中,run()方法没有具体内容,所以用户程序需要创建自己的Thread类的子类,并重写run()方法来覆盖原来的run()方法。当run()方法执行完毕,线程就变成死亡状态,所谓死亡状态就是线程释放了实体,即释放分配给线程对象的内存。在线程没有结束run()方法之前,不赞成让线程再调用start()方法,否则将发生IllegalThreadStateException异常。
3) sleep(int millsecond)方法
如果线程在休眠时被打断,JVM就抛出InterruptedException异常。因此,必须在try~catch语句块中调用sleep方法。
4) isAlive()方法
线程处于“新建”状态时,线程调用isAlive()方法返回false。当一个线程调用start()方法,并占有CPU资源后,该线程的run()方法就开始运行,在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true。当线程进入“死亡”状态后(实体内存被释放),线程仍可以调用isAlive()方法,这时返回的值是false。
5) currentThread()方法
该方法返回当前正在使用CPU资源的线程。
6) interrupt()方法
该方法用来“吵醒”线程。当一些线程调用sleep方法处于休眠状态时,一个占有CPU资源的线程可以让休眠的线程调用interrupt方法“吵醒”自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。
5. GUI线程
当Java程序包含图形用户界面(GUI)时,Java虚拟机(JVM)在运行应用程序时会自动启动更多的线程,其中有两个重要的线程:AWT-EventQuecue和AWT-Windows。AWT-EventQuecue线程负责处理GUI事件,AWT-Windows线程负责将窗体或组件绘制到桌面。JVM要保证每个线程都有使用CPU资源的机会,当窗口需要刷新时,JVM就会将CPU资源切换给AWT-Windows线程;当程序中发生GUI界面事件时,JVM就会将CPU资源切换给AWT-EventQuecue线程,AWT-EventQuecue线程就会来处理这个事件。
6. 线程同步
在处理线程同步时,要做的第一件事就是要把修改数据的方法用关键字synchronized来修饰。一个方法使用关键字synchronized修饰后,当一个线程A使用这个方法时,其他线程想使用这个方法时就必须等待,知道线程A使用完该方法。所谓线程同步就是若干个线程都需要使用一个synchronized修饰的方法。
7. 在同步方法中使用wait()、notify()和notifyAll()方法
当一个线程使用的同步方法中用到某个变量,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用wait()方法。使用wait()方法可以中断方法的执行,使本线程等待,暂时让出CPU的使用权,并允许其他线程使用这个同步方法。其他线程如果在使用这个同步方法时不需要等待,那么它使用完这个同步方法的同时,应当用notifyAll()方法通知所有的由于使用这个同步方法而处于等待的线程结束等待。曾中断的线程就会从刚才的中断处继续执行这个同步方法,并遵循“先中断先继续”的原则。如果使用notify()方法,那么只是通知处于等待中的线程的某一个结束等待。
Wait()、notify()和notifyAll()都是Object类中的final方法,被所有的类继承、且不允许重写的方法。
8. 挂起、恢复和终止线程
有时候两个线程并不是同步的,即不涉及都需要调用一个同步方法,但线程也可能需要暂时挂起。所谓挂起一个线程就是让线程暂时让出CPU的使用权限,停止执行,但停止执行的持续时间不确定,因此,不能使用sleep方法暂停线程。挂起一个线程需使用wait()方法,即让准备挂起的线程调用wait()方法,主动让出CPU的使用权限,暂时停止执行。所谓恢复线程就是让挂起的线程恢复执行过程,即从中断处继续线程的执行。为了恢复该线程,其他线程在占有CPU资源期间,让挂起的线程的目标对象执行notifyAll()方法,使得挂起的线程继续执行;如果线程没有目标对象,为了恢复该线程,其他线程在占有CPU资源期间,让挂起的线程调用notifyAll()方法,使挂起的线程继续执行。终止线程就是让线程结束run()方法的执行进入死亡状态。
9. 计时器线程Timer
10. 线程联合
一个线程A在占有CPU资源期间,可以让其他线程B调用join()和本线程联合。如果线程A在占有CPU资源期间一旦联合线程B,那么线程A将立刻中断执行,一直等到它联合的线程B执行完毕,线程A再重新排队等待CPU资源,以便恢复执行。如果线程A准备联合的线程B已经结束,那么线程联合不会产生任何效果。
11. 守护线程