多线程


线程概述

一、进程的概念

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。简单地说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源,如CPU时间、内存空间、文件、输入输出设备的使用权等。换句话说,当程序在执行时,将会被操作系统载入内存中,并且启动它,然后就变成了所谓的“进程”。

二、线程的概念

对于完全不相关的程序而言,在同时执行时,彼此的进程也不会做数据交换的工作,而可以完全独立地运行。但是,对于同一程序产生的多个进程,通常是因为程序设计者希望能加快整体工作的效率,运用多个进程协同工作。但在进程的概念中,每一个进程的内部数据和状态都是完全独立的,所以即使它们是同一程序产生的,也必须重复许多的数据复制工作,而且在交换彼此数据的时候,也要再使用一些进程间通信的机制。为了减少不必要的负担,线程的概念也就运营而生。

运行一个进程时,程序内部的代码都是按顺序先后执行的。如果能够将一个进程划分成更小的运行单位,则程序中一些彼此相对独立的代码段就可以同时运行,从而获得更高的执行效率。线程就提供了这种同时执行的办法。

线程其实与进程相似,也是一个执行中的程序,但线程是一个比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程,形成多条执行路线。但是与进程不同的是,同类的多个线程是共享一块内存和一组系统资源,而线程本身的数据通常只有处理器的寄存器数据以及一个供程序执行时使用的堆栈。所以系统在产生一个线程,或是在在各个线程之间作切换的工作时,负担要比进程小得多。也正因为如此,线程也被称为负担轻的进程。与进程占有不同内存空间不同的是,同一进程的各个线程之间可以共享相同的内存空间(包括代码空间和数据空间),并利用这些共享内存来完成数据交换、实时通讯及必要的同步工作。由于可共享内存,所以各线程之间的通信速度很快,线程之间进行切换所占用的系统资源也较少。对每个线程来说,他都有自身的产生、存在和消亡的过程,所以,它也是一个动态的概念。

在Java程序启动时,一个进程马上启动,同时该进程会自动启动一个线程的运行,这个线程称为程序的主线程。

主线程是多线程编程的核心,它是产生其它子线程的线程。由它控制其它线程的启动,执行各种关闭操作。


线程的创建

Java语言中实现多线程的方法有两种,一种是继承java.lang包中的Thread类;另一种是用户在定义自己的类中实现Runnable接口。但不管采用哪种方法,都要用到Java语言类库中的Thread类以及相关方法。

Thread类的常用构造方法:

Thread():创建一个线程对象,此线程对象的名称是“Thread”+n的形式,其中n是一个整数。使用这个构造方法,必须创建Thread类的一个子类并覆盖其run()方法。

Thread(String name):创建一个线程名称为name的Thread对象

Thread类的常用方法:

currentThread():返回当前正在运行的线程对象

getName():返回线程的名称

start():使线程由新建状态变为就绪状态。如果该进程已经是就绪状态,则产生异常

run():如果这个线程实例是使用实现了Runnable接口的类的实例创建的,就调用这个类的实例run()方法,否则什么都不做并返回

isAlive():判断当前线程是否正在运行,若是返回true,否则返回false

interrupt():中断当前线程

isInterrupted():判断该线程是否被中断,若是返回true,否则返回false

join():暂停当前线程的执行,等待调用该方法的线程结束后再继续执行本线程

getPriority():返回线程的优先级

setPriority(int newPriority):设置线程优先级。如果当前线程不能修改这个线程,则产生SecurityException异常。如果参数不在所要求的优先级范围内,则产生异常

sleep(long millis):为当前执行的线程指定睡眠时间。参数millis是线程睡眠的毫秒数。如果这个线程已经被别的线程中断,则产生异常

yield():暂停当前线程的执行,但该线程仍处于就绪状态,不转为阻塞状态

一、继承Thread类

该类具有创建和运行线程的所有功能,通过重写该类的run()方法,实现用户所需的功能。通过实例化自定义的Thread类,使用start()方法启动线程

例:继承Thread类创建My Thread1类,显示主线程的信息,创建子线程并启动它

二、实现Runnable接口

上面介绍了如何以Thread类的方式来创建线程,但是如果我们的类本身已经继承了某个父类,由于Java语言不允许多重继承,所以就无法再继承Thread类,特别是小程序。这种情况下,可以创建一个类来实现Runnable接口。这种创建线程的方式更具有灵活性,也使得用户线程能够具有其他类的一些特性,因此这种方式是经常使用的。

Runnable接口定义在java.lang包中,其中只提供了一个抽象方法run()的声明。

Runnable是java语言中实现线程的接口,从本质上说,任何实现线程的类都必须实现该接口。其实Thread类就是直接继承了Object类,并实现了Runnable接口,所有其子类才具有线程的功能。

总之,Runnable接口只有一个方法run(),用户可以声明一个类并实现Runnable接口,并定义run()方法,将线程代码写入其中,就完成了这一部分的任务。但是,Runnable接口并没有任何对线程的支持,还必须创建Thread类的实例,这一点通过Thread类的构造方法来实现。

例:创建SimpleThread类,实现Runnable接口,并在run()方法中实现规定的输出功能

例:利用Runnable接口创建线程

例:用Runnable接口程序来模拟航班售票系统,实现3个售票窗口发售某次航班的10张机票,一个售票窗口用一个线程来表示


线程的调度

一、线程的生命周期

线程从创建到死亡的这个过程称为线程的一个“生命周期”。在某个时间点上,线程具有不同的状态。

1、创建状态:当使用线程类的构造函数创建某个线程类的对象时,线程处于创建状态,在调用了对象的start()方法后,线程进入可执行状态。

2、可执行状态:在线程进入可执行状态后,如果系统的CPU空闲,则线程就可以直接投入运行了。在线程运行时,如果调用线程的wait()方法或者sleep()方法,则线程进入非可执行状态,此时系统的CPU不再分配时间片给该线程。

3、非可执行状态:在线程进入非可执行状态后,可以通过调用线程的notify()方法或者notifyAll()方法、interrupt()方法再次进入可执行状态。

4、终止状态:当线程的run()方法执行完毕后,线程自动消亡,该线程占用的系统资源会自动释放。该线程的整个生命周期就此结束。

二、线程的优先级

线程的优先级是通过Thread类中定义的常量来实现的:

MAX_PRIORITY 线程的最高优先权,代表常量值10

NORM_PRIORITY 线程的默认优先级,代表常量值5

MIN_PRIORITY 线程的最低优先级,代表常量值1

设置和获取线程优先级的方法:

void setPriority(int newPriority)

int getPriority()

三、线程的同步

为了处理线程对共享资源的竞争,Java语言提供了线程的同步机制。通过同步机制可保证多个线程同时访问一个对象时,可保持对象数据的统一性和完整性。

使用Java语言提供的关键字synchronized,可以采用同步方法或同步代码块的方法实现线程的同步。

1、同步方法

同步方法是指将访问共享资源的方法都标记为synchronized,这样当某个线程调用了该方法后,其它调用该方法的线程将进入阻塞状态,直到原线程完成对synchronized方法的调用为止。

例:创建两个线程,同时调用某个类的prin()方法,把print()方法定义为同步和非同步两种方法,分析执行结果的差异

2、同步代码块

格式:

例:代码块实现线程同步的功能

四、wait-notify机制

假如有两个线程A和B,A线程需要首先访问共享资源M,然后访问共享资源N;线程B也需要访问共享资源M和N。现在,A线程已经拥有资源M的控制权,需要资源N,线程才能正常运行;而线程B已经获得资源N的控制权,需要资源M。此时,线程A在等待线程B释放资源N,而线程B在等待线程A释放资源M。这样两个线程互相等待,永远不会结束,程序进入“死锁”状态。

为了解决这类问题,Java语言提供了wait-notify机制。通过使用wait()、notify()、notifyAll()方法实现线程间的通信,从而尽量避免多线程运行时出现死锁。

wait()方法通知被调用的线程放弃对共享资源的控制,进入等待状态,直到其它线程释放了共享资源并调用notify()方法。notify()方法唤醒同一对象上第一次调用了wait()方法的线程。notifyAll()方法唤醒所有调用了wait()方法的线程,此时退出睡眠状态的,优先级最高的线程将恢复执行。

注意事项:

(1)线程调用wait()方法并进入等待状态时,会释放已经控制的共享资源,必须由当前线程自己调用wait()方法,即:是线程本身在得不到需要的资源时,主动放弃对已有资源的控制,进入等待状态。

(2)线程调用notify()和notifyAll()方法时,是在当前线程已经使用完所控制的共享资源,并且已经放弃了对共享资源的控制时,通知其它线程恢复执行。

(3)线程不能自己调用notify()或notifyAll()方法唤醒自己,线程也不能调用wait()方法要求其它线程进入等待状态。

例:4位哲学家用4根筷子进餐的问题