第5章 多线程_补充案例

更新时间:2024-04-30 12:01:02 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

博学谷——让IT教学更简单,让IT学习更有效

第五章 补充案例

案例5-1 继承Thread类创建多线程

一、案例描述

1、 考核知识点

编 号:00105002

名 称:继承Thread类创建多线程

2、 练习目标

? 掌握如何通过继承Thread类实现多线程的创建。 ? 掌握Thread类中run()方法和start()方法的使用。

3、 需求分析

在程序开发中,会遇到一个功能需要多个线程同时执行才能完成的情况。这时,可以通过继承线程类Thread,并重写Thread类中的run()方法来实现。为了让初学者熟悉如何创建多线程,在案例中将通过继承Thread类方式创建线程,并实现多线程分别打印0~99的数字的功能。

4、 设计思路(实现原理)

1) 自定义一个类Demo,使其继承Thread类。

2) 在Demo类中重写run()方法,在run()方法内编写一个for循环,循环体内打印:“Demo:”+

当前循环次数。

3) 编写测试类Example01,在Example01类的main()方法中,创建一个Demo对象,并执行其

start()方法,接着编写一个for循环,循环体内打印:“main:”+当前循环次数。

二、案例实现

class Demo extends Thread { }

public class Example01{

public static void main(String[] args) {

Demo d = new Demo(); d.start();

for(int x=0; x<100; x++){ }

1

System.out.println(\

public void run() {

for (int x = 0; x < 100; x++) {

}

System.out.println(\

}

博学谷——让IT教学更简单,让IT学习更有效

}

}

运行结果如图5-1所示。

图5-1

运行结果

三、案例总结

1、通过继承Thread类,并重写Thread类中的run()方法可以实现多线程。

2、Thread类中,提供的start()方法用于启动新线程,线程启动后,系统会自动调用run()方法。 3、main()方法中有一条主线程在运行。

案例5-2 实现Runnable接口创建多线程

一、案例描述

1、 考核知识点

编 号:00105003

名 称:实现Runnable接口创建多线程

2、 练习目标

? 掌握如何通过实现Runnable接口方式创建多线程。

? 掌握如何使用Thread类的有参构造方法创建Thread对象。

3、 需求分析

在Java中只支持单继承,因此通过继承Thread类创建线程有一定的局限性,这时可以使用另一种方式,即实现Runnable接口来创建线程。通过这种方式需要在Thread(Runnable target)的构造方法中,传递一个实现了Runnable接口的实例对象。接下来在案例中将通过实现Runnable接口方式创建线程,并实现多线程分别打印0~99的数字的功能。

4、 设计思路(实现原理)

1) 自定义一个类Demo,使其实现Runnable接口。

2) 在Demo类中覆写run()方法,在方法编写一个for循环,循环体内打印:当前线程名称:+

当前循环次数。

3) 编写测试类Example02,在Example02类的main()方法中,创建一个Demo对象,利用Thread

(Runnable target)构造方法创建2个线程对象,分别命名为“蜘蛛侠”和“钢铁侠”,并执行线程对象的start()方法,同时编写for循环,循环内打印“main:”+当前循环次数。

2

博学谷——让IT教学更简单,让IT学习更有效

二、案例实现

class Demo implements Runnable { }

public class Example02 { }

public static void main(String[] args) { }

Demo d = new Demo();

Thread t1 = new Thread(d,\蜘蛛侠\Thread t2 = new Thread(d,\钢铁侠\t1.start(); t2.start();

for (int x = 0; x < 100; x++) { }

System.out.println(\

public void run() { }

for(int x=0; x<100; x++){ }

System.out.println(Thread.currentThread().getName()+\

运行结果如图5-2所示。

图5-2

运行结果

三、案例总结

1、可以把实现了Runnable接口并重写run()方法的实例对象,作为Thread有参构造方法的参数来创建多线程程序。 2、使用Thread类的构造方法Thread(Runnable target, String name)创建线程对象时,还可以给线程指定新名称。

3、思考一下:既然有了继承Thread类的方式,为什么还要有实现Runnable接口的方式?

a) 可以避免由于Java的单继承带来的局限性。在开发中经常碰到这样一种情况,就是使用一个已经继承了某一个类的子类创建线程,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么就只能采用实现Runnable接口的方式。

3

博学谷——让IT教学更简单,让IT学习更有效

b) 实现接口的方式,适合多个相同程序代码的线程去处理同一个资源的情况,把线程同程序代码、数据有效的分离,很好的体现了面向对象的设计思想。例如:一个售票程序继承了Thread类,在售票时启动了多个售票程序,但他们不是同一个对象,数据没有共享,这样就会出现票数重复出售的情况;而当售票程序实现Runnable接口后,多个线程运行同一个售票程序,实现了票数共享的好处。

案例5-3 设置后台线程

一、案例描述

1、 考核知识点

编 号:00105005 名 称:后台线程

2、 练习目标

? 了解后台线程的生命周期

? 掌握如何将线程设置为后台线程

3、 需求分析

默认情况下,新创建的线程都是前台线程,若想使前台线程变为后台线程,可以使用setDaemon(true)方法实现,为了让初学者熟悉后台线程,案例中将通过设置一个后台线程并演示后台线程和程序结束之间的关系。

4、 设计思路(实现原理)

1) 自定义一个类Watcher,使其实现Runnable接口。

2) 在Watcher类中覆写run()方法,在方法内编写一个for循环,循环体内打印:线程名称+循环

次数。

3) 编写测试类Example03,在Example03类的main()方法中,创建一个Watcher对象,利用Thread

(Runnable target)构造方法创建线程对象并命名,将线程设置为后台线程,执行该线程的start()方法,接着编写一个for循环,循环内打印循环次数。

二、案例实现

class Watcher implements Runnable { }

public class Example03 {

public static void main(String[] args) {

Watcher watcher = new Watcher(); Thread t = new Thread(watcher,\星矢\t.setDaemon(true);

4

public void run() { }

for (int x = 0; x < 1000; x++) { }

System.out.println(\我是\

+\守护者,\我在守护雅典娜\

博学谷——让IT教学更简单,让IT学习更有效

}

t.start();

for (int i = 3; i >0; i--) { }

}

System.out.println(\我是雅典娜女神,我马上死了!\if(i==1){

System.out.println(\我是雅典娜女神,我死了!\}

运行结果如图5-3所示。

图5-3

运行结果

三、案例总结

1、在多线程程序中,一旦前台线程(例如主线程)结束,后台线程也就结束了。

2、要将某个线程设置为后台线程,该线程的setDaemon()方法必须在start()方法之前调用,否则会引发IllegalThreadStateException异常。

案例5-4 线程的优先级

一、案例描述

1、 考核知识点

编 号:00105007 名 称:线程的优先级

2、 练习目标

? 了解线程中优先级的概念和作用 ? 掌握设置线程优先级的方法

? 掌握线程设置优先级方法中的三个静态常量

3、 需求分析

在应用程序中,如果要对线程进行调度,最直接的方式就是设置线程的优先级。这时,可以通过线程的setPriority()方法来设置线程优先级别,实现对线程的调度功能。为了让初学者掌握线程的优先级,在案例中创建3个线程,分别为它们设置不同的优先级来演示不同优先级线程的调度。

4、 设计思路(实现原理)

1) 自定义一个类Demo,使其实现Runnable接口。

5

博学谷——让IT教学更简单,让IT学习更有效

2) 在Demo类中重写run()方法,在方法内编写一个for循环,循环体内打印:线程名称+循环

次数。

3) 编写测试类Example04,在Example04类的main()方法中,创建一个Demo对象,利用Thread

的构造方法创建三个线程对象并命名,使用setPriority()方法将其中两个线程的优先级设为最大和最小,最后开启三个线程的start()方法。

二、案例实现

class Demo implements Runnable { }

public class Example04 { }

public static void main(String[] args) { }

Demo d = new Demo();

Thread t1 = new Thread(d,\杨过\Thread t2 = new Thread(d,\岳不群\Thread t3 = new Thread(d,\金龙\//设置线程的优先级

t3.setPriority(Thread.MAX_PRIORITY); t2.setPriority(1); t1.start(); t2.start(); t3.start(); public void run() { }

for (int x = 0; x < 5; x++) { }

System.out.println(Thread.currentThread().getName() + \

运行结果如图5-4所示。

图5-4

运行结果

6

博学谷——让IT教学更简单,让IT学习更有效

三、案例总结

1、在多线程程序中,可以通过设置线程的优先级别来控制线程获得CPU执行的几率。

2、线程的优先级用1~10之间的整数来表示,数字越大优先级越高。也可以使用静态常量表示线程的优先级,如:MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY。

3、虽然Java中提供了10个线程优先级,但是这些优先级需要操作系统的支持,不同的操作系统对优先级的支持是不一样的,不能很好的和Java中线程优先级一一对应,因此,在设计多线程应用程序时,其功能的实现一定不能依赖于线程的优先级,而只能把线程优先级作为一种提高程序效率的手段。

案例5-5 线程休眠

一、案例描述

1、 考核知识点

编 号:00105008 名 称:线程休眠

2、 练习目标

? 了解线程休眠的概念和作用

? 掌握如何使用sleep(long millis)方法使线程休眠

3、 需求分析

如果希望人为地控制线程,使正在执行的线程暂停,将CPU让给别的线程。这时,可以使用静态方法sleep(long millis),该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态。为了让初学者更好地掌握线程休眠,案例中将启动三个线程共同出售10张票来演示线程休眠及休眠引发的结果。

4、 设计思路(实现原理)

1) 自定义一个类Ticket,使其实现Runnable接口,并在该类中创建int类型私有属性tickets,

赋初值为10。

2) 在Ticket类中重写run()方法,在方法内编写一个while循环。循环体内判断ticket值,当大

于0时,使用sleep(long millis)方法使线程休眠1秒钟,并打印:当前线程名称+“正在出售第”+循环次数;否则结束循环。每执行一次while循环,tickets值减一。 3) 编写测试类Example05,在Example05类的main()方法中,创建一个Ticket对象,利用Thread

的构造方法创建三个线程对象并命名,并开启三个线程的start()方法。

二、案例实现

class Ticket implements Runnable {

private int tickets = 10; public void run() {

while (true) {

if (tickets > 0) {

try {

7

博学谷——让IT教学更简单,让IT学习更有效

}

public class Example05 { }

public static void main(String[] args) { }

Ticket tr = new Ticket(); Thread t1 = new Thread(tr); Thread t2 = new Thread(tr); Thread t3 = new Thread(tr); t1.setName(\窗口1\t2.setName(\窗口2\t3.setName(\窗口3\t1.start(); t2.start(); t3.start(); }

}

}

}

System.out.println(Thread.currentThread().getName() + \正在出售第\张票\

Thread.sleep(1000); e.printStackTrace();

} catch (InterruptedException e) {

运行结果如图5-5所示。

图5-5 运行结果

从运行结果可以看出,当程序启动后,“窗口1”、“窗口2”、“窗口3”线程共同出售tikcets,但是最后“窗口1”出售了第0张票、“窗口3”出售了第-1张票,从中可以推断当tickets=1时,某一个线程进入if分支语句后,线程休眠了1秒。在此期间,tickets的值依然为1,其他两个线程也顺利进入了到if分支语句中,当线程休眠时间结束后,三个线程分别操作了ticket值,所以造成了tickets值为负数。

三、案例总结

1、sleep(long millis)方法声明抛出InterruptedException异常,因此在调用该方法时应该捕获异常,或者声明抛出该异常。

8

博学谷——让IT教学更简单,让IT学习更有效

2、sleep()是静态方法,只能控制当前正在运行的线程休眠,而不能控制其它线程休眠。当休眠时间结束后,线程就会返回到就绪状态,而不是立即开始运行。

案例5-6 线程让步

一、案例描述

1、 考核知识点

编 号:00105009 名 称:线程让步

2、 练习目标

? 了解线程让步的概念和作用 ? 掌握设置线程让步的方法

3、 需求分析

在校园中,我们经常会看到同学互相抢篮球,当某个同学抢到篮球后就可以拍一会,之后他会把篮球让出来,大家重新开始抢篮球,这个过程就相当于Java程序中的线程让步。在多线程程序中,可以通过线程的yield()方法将线程转换成就绪状态,让系统的调度器重新调度一次,达到线程让步的目的。案例中将在一个多线程程序中,通过yield()方法对其中一个线程设置线程让步来演示。

4、 设计思路(实现原理)

1) 自定义一个类Demo,使其实现Runnable接口。

2) 在Demo类中覆写run()方法,在方法内编写一个for循环,循环体内,先执行线程让步的方

法yield(),然后输出打印:线程名称+循环次数。 3) 编写测试类Example06,在Example06类的main()方法中,创建一个Demo对象,利用Thread

的构造方法创建两个线程对象,并执行线程对象的start()方法,同时编写for循环,循环内打印“main:”+当前循环次数。

二、案例实现

class Demo implements Runnable{ }

public class Example06 {

public static void main(String[] args)throws Exception {

Demo d = new Demo(); Thread t0 = new Thread(d); Thread t1 = new Thread(d);

9

public void run(){ }

for(int x = 0 ; x < 5 ; x++){ }

Thread.yield();

System.out.println(Thread.currentThread().getName()+\

博学谷——让IT教学更简单,让IT学习更有效

}

}

t0.start(); t1.start();

for(int x = 0 ; x<5 ; x++){ }

System.out.println(\

运行结果如图5-6所示。

图5-6

运行结果

三、案例总结

1、在多线程程序中,可以通过设置线程让步,让系统的调度器重新调度一次CPU的分配。

2、线程让步和线程休眠是不一样的,线程让步不会阻塞该线程,它只是将线程转换成就绪状态,而线程休眠,是让线程在一定时间内进入休眠等待状态,到达时间后线程再转换成就绪状态。

案例5-7 线程插队

一、案例描述

1、 考核知识点

编 号:00105010 名 称:线程插队

2、 练习目标

? 了解线程插队的概念和作用 ? 掌握线程插队方法的使用

3、 需求分析

在火车站买票的时候,有的乘客着急赶火车,会插到队伍前面先买车票,其他乘客再买票。那么在多线程程序中,也可以通过线程插队,让插队的线程先执行完,然后本线程才开始执行。在案例中将通过使用join()方法来演示线程插队。

4、 设计思路(实现原理)

1) 自定义一个类Demo,使其实现Runnable接口。

10

博学谷——让IT教学更简单,让IT学习更有效

2) 在Demo类中覆写run()方法,在方法内编写一个for循环,循环体内打印:线程名称+循环

次数。

3) 编写测试类Example07,在Example07类的main()方法中,创建一个Demo对象,利用Thread

的构造方法创建两个线程对象,分别命名“排队队员”和“插队队员”,然后编写两个线程对象的start()方法,然后调用“插队队员”线程的join()方法。

二、案例实现

class Demo implements Runnable { }

public class Example07 { }

public static void main(String[] args) { }

Demo jd = new Demo(); Thread t1 = new Thread(jd); Thread t2 = new Thread(jd); t1.setName(\排队队员\t2.setName(\插队队员\t1.start(); t2.start(); try { }

t2.join();

e.printStackTrace();

} catch (InterruptedException e) { public void run() { }

for (int x = 0; x < 5; x++) { }

System.out.println(Thread.currentThread().getName() + \

运行结果如图5-7所示。

图5-7

运行结果

11

博学谷——让IT教学更简单,让IT学习更有效

三、案例总结

1、线程插队,可以让插队的线程先执行完,然后本线程才开始执行。 2、使用线程插队join()方法时,需要抛出InterruptedException异常。

案例5-8 同步代码块的使用

一、案例描述

1、 考核知识点

编 号:00105012 名 称:同步代码块

2、 练习目标

? 掌握同步代码块作用

? 掌握同步代码块的使用方法 ? 掌握同步代码块中锁对象的使用

3、 需求分析

生活中,会遇到两人上洗手间的问题,甲使用洗手间的时候会锁上门,乙看到门锁上了,就需要等甲使用完后再使用的。那么在多线程程序中,可以通过将共享资源放在同步代码块内来实现多个线程同步处理共享资源的问题。本案例将通过两个线程共享资源来演示同步代码块的使用。

4、 设计思路(实现原理)

1) 自定义一个类Demo,使其实现Runnable接口。 2) 在Demo类中覆写run()方法,在方法内编写synchronized同步代码块,在进入同步代码块时,

打印线程名称,然后编写一个for循环,循环体内打印:运行线程名称+循环次数。当循环次数等于3时,跳出循环。 3) 编写测试类Example08,在Example08类的main()方法中,创建一个Demo对象,利用Thread

的构造方法创建两个线程对象,分别命名“张三”和“李四”,执行两个线程的start()方法。

二、案例实现

class Demo implements Runnable {

private Object obj = new Object(); public void run() {

synchronized (obj) {

System.out.println(Thread.currentThread().getName()

+ \进入洗手间,门以锁上\

for (int i = 1; i < 10; i++) {

System.out.println(Thread.currentThread().getName()

+ \正在使用洗手间\

if (i == 3) {

12

博学谷——让IT教学更简单,让IT学习更有效

}

public class Example08 { }

public static void main(String[] args) { }

Demo d = new Demo();

Thread t1 = new Thread(d, \张三\Thread t2 = new Thread(d, \李四\t1.start(); t2.start(); }

}

}

}

System.out.println(Thread.currentThread().getName()

+ \用完,准备出去,锁打开\

break;

运行结果如图5-8所示。

图5-8

运行结果

三、案例总结

1、同步代码块中的锁对象可以是任意类型的对象,但多个线程共享的锁对象必须是唯一的。

2、锁对象的创建代码不能放到run()方法中,否则每个线程运行到run()方法都会创建一个新对象,这样每个线程都会有一个不同的锁,每个锁都有自己的标志位。线程之间便不能产生同步的效果。

案例5-9 同步代码块嵌套造成死锁

一、案例描述

1、 考核知识点

编 号:00105014 名 称:死锁

2、 练习目标

? 了解什么是死锁和造成死锁的原因

13

博学谷——让IT教学更简单,让IT学习更有效

3、 需求分析

在编写多线程程序中,经常出现多个同步代码块嵌套的情况,而此时如果没有控制好锁对象的一致性就会出现死锁现象,接下来,在案例中通过同步代码块之间的互相嵌套来演示线程死锁。

4、 设计思路(实现原理)

1) 自定义一个类DieLock,使其继承Thread。

2) 在DieLock类中创建两个静态常量objA、objB,分别是a锁、b锁。然后创建一个boolean

类型的私有属性flag作为标示符,并编写DieLock的构造方法

3) 重写run()方法,在方法内首先判断flag的值。当flag值为true时,分别以objA和objB 为

锁对象,编写两个嵌套的同步代码块,并在同步代码块中打印flag值和锁对象名称;当flag为值为false时,编写flag为false时的代码,只是把锁对象的顺序更换一下。

4) 编写测试类Example09,在Example09类的main()方法中,利用DieLock的构造方法,创建

两个DieLock对象,传入的flag值分别为true和false,执行两个线程的start()方法。

二、案例实现

class DieLock extends Thread { }

public class Example09 {

public static void main(String[] args) {

new DieLock(true).start();

14

private boolean flag;

static Object objA = new Object(); static Object objB = new Object(); public DieLock(boolean flag) { }

public void run() { }

if (flag) { }

synchronized (objA) { }

synchronized (objB) { }

System.out.println(\synchronized (objA) { }

System.out.println(\System.out.println(\synchronized (objB) { }

System.out.println(\

this.flag = flag;

} else {

博学谷——让IT教学更简单,让IT学习更有效

}

new DieLock(false).start(); }

运行结果如图5-9所示。

图5-9

运行结果

三、案例总结

当两个线程的同步代码块彼此拿着对方需要的锁时,程序不会停止,但会一直“卡顿”,这种现象就是“死锁”现象。

案例5-10 多线程通信

一、案例描述

1、 考核知识点

编 号:00105015 名 称:多线程通信

2、 练习目标

? 掌握如何解决线程通信中的共享资源的安全问题

3、 需求分析

一条生产线的上下两个工序,它们必须以规定的速率完成各自的工作,才能保证产品在流水线中顺利的流转。在多线程的程序中,上下工序可以看作两个线程,这两个线程之间需要协同完成工作,就需要线程之间进行通信。了让初学者掌握多线程通信,案例中将通过生产和消费鼠标这两个过程的多线程协调来演示。

4、 设计思路(实现原理)

1) 创建一个产品类Product,该类有三个属性分别是:产品名称name,产品数量count,和一个boolean类型的标示量flag。当flag值为false时,说明产品未生产,当flag值为true时,说明产品已生产。

2) 在Product类中编写一个同步方法set(),每次调用set()方法时,表示要生成一个产品。在set()方法中,编写一个while循环,当flag值为true时,调用该线程的wait()方法,让线程等待。在while循环体外,count值递增,并打印当前线程名称和产品名称及数量,最后将flag值设为true,并唤醒所有的线程。

3) 在Product类中编写一个同步方法get(),每次调用get()方法时,表示要消费一件产品。在get()方法中,编写一个while循环,当flag值为false时,调用该线程的wait()方法,让线程等待。在循环体外,打印当前线程名称和被消费的产品名称和个数。最后将flag值设为false,并唤醒所有的线程。

4) 编写一个生产类Producter,该类定义了一个Product类型的成员变量和有参构造方法,构造方法中的参数用于为Product对象赋值,并且Producter类实现了Runnable接口。

15

本文来源:https://www.bwwdw.com/article/r5ng.html

Top