Java最新实用教程第九章 多线程
Java最新实用教程
2009年 7月 27日星期一 2
学习目的:
掌握多线程的基础知识
掌握线程的生命周期
了解线程的优先级
了解用线程制作动画的方法
学习重点:
多线程的基础知识
线程的生命周期
Java最新实用教程
2009年 7月 27日星期一 3
第 九 章 多线程本章提要:
1,多线程编程基础线程的基本概念,线程模型,Thread类,Runnable接口,
线程间的数据共享,多线程的同步控制与通信和守护线程
2.线程的生命周期线程的几种基本状态,死锁问题和控制线程的生命
3.线程的优先级
4.用于制作动画的线程动画程序框架,帧的画法,避免闪动与使用图片
Java最新实用教程
9.1 多线程编程基础
Java与现在流行的通用目的编程语言相比有个独特之处,它使用程序员可以使用的并发原语。程序员指定在程序中包含要执行的多个线程,每个线程设计成程序的一部分,并且能与其他线程并发执行,这种功能称为多线程。
Java最新实用教程
2009年 7月 27日星期一 5
应用程序存储在计算机系统的存储空间中的一段静态的代码序列
进程处在可执行状态中的应用程序称为进程。从用户角度来看,进程是应用程序的一个执行过程。从操作系统核心角度来看,进程代表的是操作系统分配的内存,CPU时间片等资源的基本单位,是为正在运行的程序提供的运行环境。
线程
Java语言中定义的线程( Thread)包括一个内存入口点地址、一个出口点地址以及能够顺序执行的代码序列。但线程不能够单独执行,它必须运行在处于活动状态的应用程序进程中,因此可以定义线程是程序内部的具有并发性的顺序代码流

9.1.1线程的基本概念
Java最新实用教程
2009年 7月 27日星期一 6
多线程模型的同步方式。
所有的对象都有充当互斥锁的内置侦听器。在给定的时刻,
任何侦听器只能由一个线程拥有。通过使用 synchronized关键字对方法进行修饰,可以启用锁定特性。在调用同步方法时,
该对象就被锁定,而试图访问该对象的其他线程就只能等待。
Java对同步的支持可以通过 wait(),notify()和 notifyAll()方法来实现。
线程类( Thread)
线程类封装了所有需要的线程操作控制。必须很清楚地区分线程对象和运行线程,线程对象可以看作是运行线程的控制面板。线程类是控制线程行为的惟一手段。一个 Java程序启动后,
就已经有一个线程在运行。可以通过调用 Thread.currentThread()
方法来查看当前运行的是哪一个线程例 9-1
9.1.2Java的线程模型
Java最新实用教程
2009年 7月 27日星期一 7
Thread类的构造函数:
Public Thread(String threadName):用于构造一个名为
threadName的 Thread对象
public Thread():则用于构造名为,Thread-”加上一个数字的线程,如,Thread-1”、,Thread-2”等。
Run方法可以在 Thread的子类中覆盖或在 Runnable对象中覆盖。
程序通过调用线程的 start方法执行线程,而 start则调用 Run
方法。当 start方法启动线程后,start便立即返回到其调用者。
调用者与这一线程并发执行。如果 start方法试图启动一个已经执行的线程,则会抛出 IllegalThreadStateException异常。
9.1.3Thread类
Java最新实用教程
2009年 7月 27日星期一 8
public static native void sleep(long millis) throws
InterruptedException:该方法中的 millis参数指出当前执行的线程应休眠多长时间(单位为 ms)。线程休眠时不会竞争处理器,因此处理器便可以运行其他线程。
Interrupt方法用于中断一个线程。如果线程已经被中断
,该方法便返回 true,否则返回 false。程序可以调用
isInterrupted方法确定该线程是否被中断。
setName方法用于设置线程的名称;
getName方法用于返回线程的名称;
toString方法返回一个包含线程名称、线程优先级和线程所属线程组的字符串;
currentThread方法返回一个对当前线程的引用。
9.1.3Thread类
Java最新实用教程
2009年 7月 27日星期一 9
创建新的执行线程的方法:
创建一个 Thread类的子类,该类重载 run方法即可
实现 Runnable 接口,并实现 run方法。
Runnable的使用使用 Runnable对象的程序会创建一个 Thread对象,并把
Runnable对象与该 Thread对象关联。 Thread类提供了两个构造函数实现和 runnable对象相关联
public Thread(Runnable runnableObject):将 runnableObject
对象的 run方法注册为线程开始执行时调用的方法。
public Thread(Runnable runnableObject,String threadName)
:以 threadName为名称构造一个线程,并将参数
runnableObject对象的 run方法注册为线程开始执行时调用的方法。
例 9-2
9.1.4 Runnable接口
Java最新实用教程
2009年 7月 27日星期一 10
冲突的产生如果应用程序是多线程的应用程序,一个 Java程序的多线程之间可以共享数据,就存在两个或更多个线程同时使用相同且有限的资源的可能性。
冲突的避免在 Java内部内置了一种机制,可防止资源的冲突。
当定义一个类的时候,常常会将类的某些属性声明为
private,并且由函数来访问其内容,同样,可以将某个函数声明为 synchronized(同步化)来防止冲突的发生。这样,每个对象就都含有一个机锁,当调用任何
synchronized函数时,对象便被锁定,该对象的所有
synchronized函数都无法再被调用,直到第一个函数执行完毕并解除锁定为止。
9.1.5 线程间的数据共享
Java最新实用教程
2009年 7月 27日星期一 11
多线程同步控制机制当第一个线程读取完数据时,第二个线程才能处理该数据。
同步控制的实现在 Java的关键字中,有一个,synchronized”的关键字,
它就可以实现同步,实现对共享数据协调操作。在声明一个类时,用 synchonized修饰的方法为同步方法。 Java中有一个同步模型 -监视器,负责管理线程对象中的同步方法的访问,它的原理是:赋予该对象惟一的一把“钥匙”,当多个线程进入对象
,只有取得该对象钥匙的线程才可以访问同步方法,其他线程都必须等待。直到该线程用 wait()方法放弃这把钥匙,其他等待的线程抢占该钥匙,抢占到钥匙的线程才可得以执行,而没有取得钥匙的线程仍被阻塞,必须继续等待。
例 9-3
9.1.6 多线程的同步控制
Java最新实用教程
2009年 7月 27日星期一 12
用于在不同线程( threads)间进行通信采用是的管道流。管道流是一种特殊的流。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。
管道的创建与使用
Pipedinputstream代表了数据在管道中的输出端,也就是线程向管道读数据的一端; pipeoutputstream代表了数据在管道中的输入端
,也就是线程向管道写数据的一端,这两个类一起使用可以提供数据的管道流。要创建一个管道流,必须先创建一个 pipeoutstream对象,然后,创建 pipeinputstream对象,如下:
pipeout= new pipedyoutstream();
pipein= new pipedputsteam(pipepout);
实现线程间的通信例 9-4 ythread zthread
9.1.7 线程之间的的通信
Java最新实用教程
2009年 7月 27日星期一 13
守护线程是为其他线程的运行提供服务的线程。一般在后台运行。垃圾回收器就是一个守护线程。
非守护线程一般是用户线程或用于处理 GUI事件的事件调度线程。
守护线程的定义,setDaemon(true);
如果参数为 false,则该线程为非守护线程。一个程序可以包括守护线程和非守护线程。当程序中只有守护线程时
,程序便结束运行。如果要使一个线程为守护线程,必须在调用其 start方法之前进行以上设置,否则会产生
IllegalThreadStateException异常。如果线程是守护线程,则
isDaemon方法返回 true,否则返回 false。
9.1.8 守护线程
Java最新实用教程
9.2 线程的生命周期任何一个线程都有自己的生命周期
,其中包括创建、可运行、运行中、等待、休眠、阻塞和死亡 7种状态。在线程的生命周期中,任何一个线程都是处于这七种状态中的某一种状态,其状态并随程序的执行在不断的变化。
Java最新实用教程
2009年 7月 27日星期一 15
9.2.1 线程的几种基本状态
Java最新实用教程
2009年 7月 27日星期一 16
9.2.1 线程的几种基本状态
创建状态:使用 new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,
称该线程处于创建状态。在调用线程的 start方法之前,该线程将一直处于创建状态( new
thread)。
可运行状态:使用 start()方法启动一个线程后,系统为该线程分配了除 CPU外的所需资源,使该线程处于可运行状态( Runnable)。
运行中状态,Java运行系统通过调度选中一个可运行的线程,使其占有 CPU并转为运行中状态
( Running)。此时,处于可运行状态的最高优先级线程便进入了运行状态,系统真正执行线程的 run()方法,即线程开始执行。
死亡状态:如果线程的 run方法结束或由于其他原因终止,则该线程便进入了死亡状态;系统最终会抛出死亡的线程。
阻塞状态:如果处于运行状态的线程发出输入 /输出请求,它便进入阻塞状态。当其等待的 I/O
操作结束时,阻塞的线程便回到可运行状态。对于阻塞的线程来说,即使处理器空闲,它也不能使用。
休眠状态:当程序调用一个正在运行的线程的 sleep方法时,该线程便会进入休眠状态。进入休眠的线程在其预先设置的休眠时间到了以后,便进入可运行状态。对于休眠的线程来说,即使处理器空闲,它也不能用。如果程序调用一个休眠线程的 interrupt方法,该线程会退出休眠状态,并进入可运行状态准备执行。
等待状态:当处于运行状态的线程调用 wait方法时,线程便进入等待状态。它将按次序在等待队列中,队列中的线程均是由于某个对象调用了 wait方法才进入等待状态的。当与某个对象相关的另一个线程调用了 notifyAll方法后,等待该对象的所有线程都回到可运行状态。
Java最新实用教程
2009年 7月 27日星期一 17
9.2.2 死锁问题
死锁的定义:,线程 A可能会陷入对线程 B的等待,而线程 B同样陷入对线程 C的等待,依此类推,整个等待最后又回到了线程 A
,于是各线程便陷入一个彼此等待的轮回中,任何线程都动弹不得,这种现象便称为死锁。
导致死锁的情况:
相互排斥:一个线程永远占有一共享资源,例如,独占该资源。
循环等待:线程 A等待线程 B,而线程 B又在等待线程 C,而线程 C又在等待线程 A。
部分分配:资源被部分分配。例如,线程 A和 B都需要访问一个文件,并且都要用到打印机,线程 A获得了文件资源,线程 B获得了打印机资源,
但是两个线程不能获得全部的资源。
缺少优先权:一个线程访问了某个资源,但是一直不释放该资源,即使该线程处于阻塞状态。
Java最新实用教程
2009年 7月 27日星期一 18
9.2.3 控制线程的生命
start( )方法:使该线程开始执行; Java 虚拟机调用线程 run 方法。
run( )方法:当编写 Thread类的子类时,该方法必须进行覆写,把要在多个线程中并行处理的代码放到这个函数中。
sleep( )方法,Thread类的静态方法,可以被类名直接调用,作用是使线程睡眠一段时间,
单位为毫秒;当调用 sleep ()函数后,线程不会释放它的“锁标志”。
例 9-5
Java最新实用教程
2009年 7月 27日星期一 19
9.2.3 控制线程的生命
wait( )方法:通过 wait()函数,可使本线程中断方法的执行,使本线程等待,暂时让出 CPU的使用权,并允许其他线程使用这个同步方法。当调用 wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其他 synchronized数据可被别的线程使用。
notify( )方法:当对一个对象执行 notify()时,会从线程等待池中移走该对象任意一个线程,并把它放到锁标志等待池中;当对一个对象执行 notifyAll()时,
会从线程等待池中移走所有该对象的所有线程,并把它们放到锁标志等待池中。
例 9-6 TestThreadMethod
Java最新实用教程
2009年 7月 27日星期一 20
9.2.3 控制线程的生命
yield()方法:通过 yield ()函数,可使线程进入可执行状态,排程器从可执行状态的线程中重新进行排程。所以调用了 yield()的函数也有可能马上被执行;当调用 yield ()函数后,线程不会释放它的“锁标志”。
stop()方法:强迫线程终止执行。
例 9-7 TestThreadMethod
Java最新实用教程
9.3 线程的优先级线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得 CPU 时间时,线程调度系统根据各个线程的优先级来决定给谁分配 CPU 时间,优先级高的线程有更大的机会获得 CPU 时间,优先级低的线程也不是没有机会,只是机会要小一些。
线程的优先级是从 1到 10之间的一个数字,越大表明任务越紧急,默认情况是 5。 JVM标准首先调用优先级较高的线程;然后才调用优先级较低的线程。但是,该标准对具有相同优先级的线程的处理是随机的。如何处理这些线程取决于基层的操作系统策略。
在某些情况下,优先级相同的线程分时运行;在另一些情况下,线程将一直运行到结束。请记住,Java 支持 10个优先级,基层操作系统支持的优先级可能要少得多,这样会造成一些混乱。因此,只能将优先级作为一种很粗略的工具使用。最后的控制可以通过明智地使用 yield()函数来完成。
例 9-8
Java最新实用教程
9.4 用于制作动画的线程在 Java中,实现动画的方法很多,如采用 Java
3D技术播放动画或利用 Quicktime for Java技术实现该动画程序等等。在本节中,Java动画的实现采用
Java.awt包中 Graphics类的 drawImage()方法在屏幕画出图像,然后通过定义一个线程,让该线程睡眠一段时间,到时后再切换成另外一幅图像,如此循环,从而达到显示动画的目的。
例 9-9
9.4.1 动画程序框架
Java最新实用教程制作动画的基本程序框架:
利用 Applet的 init()方法定义帧刷新频率。
借助 Applet的 start()方法创建一个新线程来管理动画,并启动该线程。
在 run方法中实现动画的循环。
在 Applet的 paint()方法中实现图片的重画。
利用 Applet的 stop()方法停止动画。
9.4.1 动画程序框架
Java最新实用教程
2009年 7月 27日星期一 24
9.4.2 帧的画法
文字动画:用 new方法创建一个新线程,用
drawString()方法在屏幕显示相应文字,然后让线程睡眠一定时间,时间到后,改变文字,
再重显文字,如此往复实现文字动画。程序运行后,可以看到屏幕上的数字一直在变化。帧的画法部分的程序如下所示。
public void paint (Graphics g) {
g.setColor(Color.black);
g.drawString(“Frame” + frame,0,30);
}
Java最新实用教程
2009年 7月 27日星期一 25
9.4.2 帧的画法
图像动画:图像动画的实现方法与文字相似,
只不过先定义了一个 Image数组,运用
getImage()方法装载相应图像到该数组中。同样用 new方法创建一个新线程,用 drawImage ()
方法在屏幕显示相应图像,然后让线程睡眠一定时间,时间到后,改变图像,再重显图像,
如此往复实现图像动画。
Java最新实用教程
2009年 7月 27日星期一 26
9.4.3 避免闪动
造成闪动的原因:由于动画是将一些分散的静态图形合并为一个连续运动的流程。在这个过程中,存在着一个屏幕的刷新动作,就会出现屏幕的闪动。
避免闪动的方法
覆盖 public void update(Graphics g)来消除闪动;
通过双缓存消除闪动。
Java最新实用教程
2009年 7月 27日星期一 27
9.4.4 使用图片
移动图片实现动画
public void update(Graphics g) { Dimension d = size();
// 创建图片对象
if ((offGraphics = = null) | | (d.widht !=
offDimension.width)| | (d.height != offDiemnsion.height)) {
offDimension = d;
offImage = createImage(d.width,d.height);
offGraphics = offImage.getGraphics(); }
// 删除前一帧的图片
offGraphics.setColor(getBackground());
offGraphics.fillRect(0,0,d.width,d.height);
offGraphics.setColor(Color.black);
// 画新的图片
paintFrame(offGraphics);
g.drawImage(offImage,0,0,null);}
Java最新实用教程
2009年 7月 27日星期一 28
9.4.4 使用图片
public void paintFrame(Graphics g) { Dimension d = size();
int w = world.getWidth(this); int h = world.getHeight(this);
if ((w > 0) && (h >0)) {g.drawImage(world,(d.width - w) / 2,
(d.height - h) / 2,this);}
w = car.getWidth(this); h = car.getHeight(this);
if ((w > 0) && (h > 0)) {w + = d.width;
// 画第一个汽车
g.drawImage(car,d.width – ((frame * 5) % w),(d.height - h) / 3,
this);
// 画第二个汽车
g.drawImage(car,d.width – ((frame * 7) % w),(d.height - h) / 2,
this);}}
另外,在 init()方法中加入两行:
world = getImage(getCodeBase(),“world.gif,);
car = getImage(getCodeBase(),“car.gif,);
Java最新实用教程
2009年 7月 27日星期一 29
9.4.4 使用图片
播放图片实现动画 。
将使用 10张图片,名字分别为 T1到 T10,所有的图片存放在一个 Image frames[ ]数组里。在初始化的时候,利用循环语句一次性地把所有图片都装载进来。源代码如下:
frames = new Image[10];
for (int i = 1; i<=10; i++) {
frame[i-1] = getImage(getCodeBase(),“T” + Ii +
“.gif”);}
在 paintFrame()方法中,利用文件名的规律,使用一个取余函数,将图片显示出来。源代码如下:
public void paintFrame(Graphics g) {
g.drawImage(frame [frame % 10 ],0,0,null);}
Java最新实用教程
2009年 7月 27日星期一 30
9.5 本章小结
深入学习了线程的基本概念、线程模型、
Thread类和 Runnable接口的相关知识
详细讨论了线程间的数据共享、多线程的同步控制、线程间的通信、守护线程、线程的生命周期、
线程的优先级和用于制作动画的线程。
在线程的生命周期中,详细讨论了线程的几种基本状态、死锁问题和控制线程的生命的方法;
在用于制作动画的线程中,主要讨论了动画程序的框架、帧的画法、避免闪动和使用图片等相关知识。