实战篇一

多线程编程时,我们需要注意遵循几个一般原则,包括安全性、可行性以及高性能,这几个原则在文档中已经得到了非常详细的解释,应该比较容易理解,这里就不多说了。
本节内容主要围绕一个例子来展开,模拟一个场景——厨师与食客,在例子中,我们理想化的认为厨师可以以固定的时间制作食物,并且不停歇,食客在固定的时间吃下固定的食物,而且食量无限,这些都是为了方便程序而做的假设。
这里先说下本例的规则,然后对整个程序进行注释分析,最后写一点总结。

规则:
食堂中有一个桌子,最多同时放10个盘子,有4个厨师做菜,200-400ms做出一盘菜,6个食客在吃,400-600ms吃完一盘,桌子上放满时厨师必须要等待,而桌上没盘子时,食客需要等待。

程序:(复制到编辑器中看效果要好些)
package debug;

import java.util.regex.*;
import java.util.*;

class Food {//创建一个食物类,代表各种食物
}
/*
* 创建一个类table,用于模拟餐桌
* 该类继承自LinkList,生成的实例相当于链表,方便存取,用于存放食物
*/

class Table extends LinkedList {
int maxSize;//定义一个变量maxSize,用于存储该餐桌最多能同时放食物的数量

public Table(int maxSize) {//有参构造器,餐桌最大存放食物量由参数传入
this.maxSize = maxSize;
}
/*
* 同步方法,用于向餐桌添加食物,加了同步锁之后,表示一个时刻只能有一个对象调用添加食物方法
* 即厨师只能一个个地将食物放在桌上,且厨师放食物时食客不能取食物
*/
public synchronized void putFood(Food f) {
while (this.size() >= this.maxSize) { //判断此时桌上食物数量是否已经达到该餐桌的最大容量
try {
this.wait();//如果达到最大容量,则添加食物的当前线程进入等待
} catch (Exception e) {
}
}
this.add(f);//如果餐桌上食物数量还不到最大容量,则向餐桌上添加一盘食物
notifyAll();//唤醒所有等待着的线程
}
/*
* 同步方法,用于从餐桌取走食物,加了同步锁之后,表示一个时刻只能有一个对象调用取走食物方法
* 即食客只能一个个地将食物从桌上取走,且食客取食物时厨师不能放食物
*/
public synchronized Food getFood() {
while (this.size() <= 0) { //判断此时桌上是否还有食物
try {
this.wait();//如果桌上食物已经被全部取走,食物数量等于0,则取食物的当前线程进入等待
} catch (Exception e) {
}
}
Food f = (Food) this.removeFirst();//否则,即桌上还有食物,则从中取走一盘食物,移除并且返回餐桌链表中的第一个元素
notifyAll();//唤醒所有等待线程
return f;//返回取走的食物
}
}
/*
* 定义一个厨师类,继承自Thread,能够创建该线程的实例
*/
class Chef extends Thread {
Table t; //将餐桌类作为一个字段
String name; //定义一个变量用于存放厨师姓名
Random r = new Random(12345); //创建一个随机数生成器r,其中参数为种子(具体含义没查,有兴趣的同学baidu下)
/*
* 有参构造器,通过调用该构造器,能够产生厨师类实例
* 同时通过参数传递,我们可以得到需要操作的餐桌类实例,以及厨师姓名
*/
public Chef(String name, Table t) {
this.t = t;
this.name = name;
}
//线程中的主体方法
public void run() {
while (true) {//定义一个无限循环,表示厨师能够不停地制作食物
Food f = make();//调用make方法,每个一段时间就制作出一盘食物
System.out.println(name + " put a Food:" + f);//输出信息,哪位厨师将哪盘食物放到了餐桌上
t.putFood(f);//调用方法,向餐桌添加一盘食物
}
}

private Food make() {//定义一个私有方法,不被外部类调用,该方法用于产生一个Food对象
try {
Thread.sleep(200 + r.nextInt(200)); //通过控制随机数的范围,可以保证每个厨师线程能在200-400ms内运行下面的代码,创建一个Food实例
} catch (Exception e) {
}
return new Food();//返回新创建的Food类对象,即厨师新制作了一盘食物
}
}

/*
* 定义一个食客类,继承自Thread,能够创建该线程的实例
*/
class Eater extends Thread {
Table t; //用于存储所要操作的餐桌类对象
String name; //定义一个变量用于存放食客姓名
Random r = new Random(54321);//创建一个随机数生成器r,其中参数为种子(具体含义没查,有兴趣的同学baidu下)

/*
* 有参构造器,通过调用该构造器,能够产生食客类实例
* 同时通过参数传递,我们可以得到需要操作的餐桌类实例,以及食客姓名
*/
public Eater(String name, Table t) {
this.t = t;
this.name = name;
}
//线程中的主体方法
public void run() {
while (true) {//定义一个无限循环,表示食客能够不停地取走食物
Food f = t.getFood(); //调用getFood方法,从餐桌上取走食物
System.out.println(name + " get a Food:" + f); //输出信息,哪位食客取走了那盘食物
eat(f);//调用eat方法,主要用来控制食客吃完一盘食物的时间
}
}

private void eat(Food f) {//定义一个私有方法,不被外部类调用,该方法用于实现一个食客吃掉一盘食物
try {
Thread.sleep(400 + r.nextInt(200));//通过控制随机数的范围,能够保证一个食客在400-600ms时间内吃完一盘食物,这个时间内该食客不会再去取食物
} catch (Exception e) {
}
}
}

public class Test {
public static void main(String[] args) throws Exception {
Table t = new Table(10);//创建一个餐桌类对象,参数10表示该餐桌上最多只能同时放10盘食物
//创建厨师(或食客)线程对象并启动线程,第一个参数表示厨师(或食客)姓名,第二个参数用于传入类中将会操作的餐桌实例,由于传入的是同一个实例,表明在这个例子中只有一个餐桌,所有的厨师的食客
//都只能对这一个餐桌进行操作
new Chef("Chef1", t).start();
new Chef("Chef2", t).start();
new Chef("Chef3", t).start();
new Chef("Chef4", t).start();
new Eater("Eater1", t).start();
new Eater("Eater2", t).start();
new Eater("Eater3", t).start();
new Eater("Eater4", t).start();
new Eater("Eater5", t).start();
new Eater("Eater6", t).start();
}
}

总结:
在该例中,我们创建了一个餐桌类实例,通过传入参数的形式,将这个实例传递给所有的厨师和食客线程中,这样能够保证所有的操作都是执行在一个餐桌类实例上。
在本例中我们为餐桌(Table)类中的getFood和putFood两个方法添加了同步关键字,我们同步的是Table类的对象,即Table的实例的同一时刻只能被一个线程(获得线程锁)所使用,避免了多个线程同时操作餐桌实例时造成的数据出错。
这只是一个简单的多线程实例,在学习过程中程涛老师也讲过类似的程序,可能有些同学还没有足够理解,所以我在这里对整个程序进行了注释,对于那些水平不错的同学,这篇文章可能就没有多大的价值了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值