多线程编程——实战篇

sapzj1984 2007-07-05 11:17:17
在进入实战篇以前,我们简单说一下多线程编程的一般原则。

  [安全性]是多线程编程的首要原则,如果两个以上的线程访问同一对象时,一个线程会损坏另一个线程的数据,这就是违反了安全性原则,这样的程序是不能进入实际应用的。

  安全性的保证可以通过设计安全的类和程序员的手工控制。如果多个线程对同一对象访问不会危及安全性,这样的类就是线程安全的类,在JAVA中比如String类就被设计为线程安全的类。而如果不是线程安全的类,那么就需要程序员在访问这些类的实例时手工控制它的安全性。

  [可行性]是多线程编程的另一个重要原则,如果仅仅实现了安全性,程序却在某一点后不能继续执行或者多个线程发生死锁,那么这样的程序也不能作为真正的多线程程序来应用。

  相对而言安全性和可行性是相互抵触的,安全性越高的程序,可性行会越低。要综合平衡。

  [高性能] 多线程的目的本来就是为了增加程序运行的性能,如果一个多线程完成的工作还不如单线程完成得快。那就不要应用多线程了。

  高性能程序主要有以下几个方面的因素:

  数据吞吐率,在一定的时间内所能完成的处理能力。

  响应速度,从发出请求到收到响应的时间。

  容量,指同时处理雅致同任务的数量。

  安全性和可行性是必要条件,如果达到不这两个原则那就不能称为真正的多线程程序。而高性是多线程编程的目的,也可以说是充要条件。否则,为什么采用多线程编程呢?



[生产者与消费者模式]

  首先以一个生产者和消费者模式来进入实战篇的第一节。

  生产者和消费者模式中保护的是谁?

  多线程编程都在保护着某些对象,这些个对象是"紧俏资源",要被最大限度地利用,这也是采用多线程方式的理由。在生产者消费者模式中,我们要保护的是"仓库",在我下面的这个例子中,

就是桌子(table)。

  我这个例子的模式完全是生产者-消费者模式,但我换了个名字。厨师-食客模式,这个食堂中只有1张桌子,同时最多放10个盘子,现在有4个厨师做菜,每做好一盘就往桌子上放(生产者将产品往仓库中放),而有6个食客不停地吃(消费者消费产品,为了说明问题,他们的食量是无限的)。

  一般而言,厨师200-400ms做出一盘菜,而食客要400-600ms吃完一盘。当桌子上放满了10个盘子后,所有厨师都不能再往桌子上放,而当桌子是没有盘子时,所有的食客都只好等待。

  下面我们来设计这个程序:

  因为我们不知道具体是什么菜,所以叫它food:

class Food{}
  然后是桌子,因为它要有序地放而且要有序地取(不能两个食客同时争取第三盘菜),所以我们扩展LinkedList,或者你用聚合把一个LinkedList作为属性也能达到同样的目的,例子中我是用

继承,从构造方法中传入一个可以放置的最大值。

class Table extends java.util.LinkedList{ int 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(); }catch(Exception e){} } Food f = (Food)this.removeFirst(); notifyAll(); return f; }
厨师类:

  由于多个厨师要往一张桌子上放菜,所以他们要操作的桌子应该是同一个对象,我们从构造方法中将桌子对象传进去以便控制在主线程中只产生一张桌子。

厨师做菜要用一定的时候,我用在make方法中用sleep表示他要消耗和时候,用200加上200的随机数保证时间有200-400ms中。做好后就要往桌子上放。

这里有一个非常重要的问题一定要注意,就是对什么范围同步的问题,因为产生竞争的是桌子,所以所有putFood是同步的,而我们不能把厨师自己做菜的时间也放在同步中,因为做菜是各自做的。同样食客吃菜的时候也不应该同步,只有从桌子中取菜的时候是竞争的,而具体吃的时候是各自在吃。所以厨师类的代码如下:

class Chef extends Thread{ Table t; Random r = new Random(12345); public Chef(Table t){ this.t = t; } public void run(){ while(true){ Food f = make(); t.putFood(f); } } private Food make(){ try{ Thread.sleep(200+r.nextInt(200)); }catch(Exception e){} return new Food(); }}
同理我们产生食客类的代码如下:

class Eater extends Thread{ Table t; Random r = new Random(54321); public Eater(Table t){ this.t = t; } public void run(){ while(true){ Food f = t.getFood(); eat(f); } } private void eat(Food f){ try{ Thread.sleep(400+r.nextInt(200)); }catch(Exception e){} }}
完整的程序在这儿:

package debug;import java.util.regex.*;import java.util.*;class Food{}class Table extends LinkedList{ int 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(); }catch(Exception e){} } Food f = (Food)this.removeFirst(); notifyAll(); return f; }}class Chef extends Thread{ Table t; String name; Random r = new Random(12345); public Chef(String name,Table t){ this.t = t; this.name = name; } public void run(){ while(true){ Food f = make(); System.out.println(name+" put a Food:"+f); t.putFood(f); } } private Food make(){ try{ Thread.sleep(200+r.nextInt(200)); }catch(Exception e){} return new Food(); }}class Eater extends Thread{ Table t; String name; Random r = new Random(54321); public Eater(String name,Table t){ this.t = t; this.name = name; } public void run(){ while(true){ Food f = t.getFood(); System.out.println(name+" get a Food:"+f); eat(f); } } private void eat(Food f){ try{ Thread.sleep(400+r.nextInt(200)); }catch(Exception e){} }}public class Test { public static void main(String[] args) throws Exception{ Table t = new Table(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(); }}


这一个例子中,我们主要关注以下几个方面:

  1.同步方法要保护的对象,本例中是保护桌子,不能同时往上放菜或同时取菜。

  假如我们把putFood方法和getFood方法在厨师类和食客类中实现,那么我们应该如此:

(以putFood为例)

class Chef extends Thread{ Table t; String name; public Chef(String name,Table t){ this.t = t; this.name = name; } public void run(){ while(true){ Food f = make(); System.out.println(name+" put a Food:"+f); putFood(f); } } private Food make(){ Random r = new Random(200); try{ Thread.sleep(200+r.nextInt()); }catch(Exception e){} return new Food(); } public void putFood(Food f){//方法本身不能同步,因为它同步的是this.即Chef的实例 synchronized (t) {//要保护的是t while (t.size() >= t.maxSize) { try { t.wait(); } catch (Exception e) {} } t.add(f); t.notifyAll(); } }}
  2.同步的范围,在本例中是放和取两个方法,不能把做菜和吃菜这种各自不相干的工作放在受保护的范围中。

  3.参与者与容积比

   对于生产者和消费者的比例,以及桌子所能放置最多菜的数量三者之间的关系是影响性能的重要因素,如果是过多的生产者在等待,则要增加消费者或减少生产者的数据,反之则增加生产者或减少消费者的数量。

  另外如果桌子有足够的容量可以很大程序提升性能,这种情况下可以同时提高生产者和消费者的数量,但足够大的容时往往你要有足够大的物理内存。

...全文
3453 26 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
亲一 2008-04-17
  • 打赏
  • 举报
回复
还是要学习操作系统...
egoxu 2008-04-16
  • 打赏
  • 举报
回复
up
changjiangzhibin 2008-04-15
  • 打赏
  • 举报
回复
mark
canghaixiaoao2 2008-04-15
  • 打赏
  • 举报
回复
mark
l_wenb 2008-04-15
  • 打赏
  • 举报
回复
支持
pleasecallmehero 2008-04-15
  • 打赏
  • 举报
回复
希望把代码贴贴好
chutou 2008-04-15
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 JackLucifer 的回复:]
好内容,要好好学习一下了。
[/Quote]

学习还是必要的
guojh021 2008-04-14
  • 打赏
  • 举报
回复
看着有点乱~~
希望把原地址搞出来~
Mr-Chen 2008-04-14
  • 打赏
  • 举报
回复
支持!
adrian_yang84 2008-04-13
  • 打赏
  • 举报
回复
感谢楼主的付出
ascent2006 2008-03-04
  • 打赏
  • 举报
回复
MARK
yangzhiqi07 2008-03-04
  • 打赏
  • 举报
回复
正学习中
qq2686 2008-03-04
  • 打赏
  • 举报
回复
就是代码乱了点
Torch009 2008-03-02
  • 打赏
  • 举报
回复
mark
leer168 2008-03-02
  • 打赏
  • 举报
回复
mark
r_swordsman 2008-03-02
  • 打赏
  • 举报
回复
只看了第一段:

在JAVA中比如String类就被设计为线程安全的类。

你凭什么说?String类就被设计为线程安全的类。

线程安全的类?哪方面线程安全?
kingtoon 2008-03-02
  • 打赏
  • 举报
回复
多谢
fflyn 2008-03-02
  • 打赏
  • 举报
回复
我在开发 .net Application时 一般用一个class SafeCall
去处理在主WorkBench调用函数.
确保在主线程上不受其他新建异步线程异常的干扰
保证如果异步线程类在有异常
但是没有catch时,
由主线程接受线程并且通常处理(一般是关闭异步线程)
这样就不用担心 由于用户态线程出错导致程序崩溃的情况

这个只是我的个人看法,
欢迎探讨
Scarroot 2008-03-02
  • 打赏
  • 举报
回复
mark
laowang2 2008-03-02
  • 打赏
  • 举报
回复
支持
加载更多回复(6)

568

社区成员

发帖
与我相关
我的任务
社区描述
英特尔® 边缘计算,聚焦于边缘计算、AI、IoT等领域,为开发者提供丰富的开发资源、创新技术、解决方案与行业活动。
社区管理员
  • 英特尔技术社区
  • shere_lin
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧