linux下C语言多线程编程实例 (转)

来源:互联网 发布:万捷科技网络验证系统 编辑:程序博客网 时间:2024/06/16 18:08

学东西,往往实例才是最让人感兴趣的,老是学基础理论,不动手,感觉没有成就感,呵呵。
下面先来一个实例。我们通过创建两个线程来实现对一个数的递加。
或许这个实例没有实际运用的价值,但是稍微改动一下,我们就可以用到其他地方去拉。
下面是我们的代码:


/*thread_example.c :  c multiple thread programming in linux    *author : falcon    *E-mail : tunzhj03@st.lzu.edu.cn    */  #include <pthread.h>  #include <stdio.h>  #include <sys/time.h>  #include <string.h>  #define MAX 10    pthread_t thread[2];  pthread_mutex_t mut;  int number="0", i;    void *thread1()  {          printf ("thread1 : I'm thread 1\n");            for (i = 0; i < MAX; i++)          {                  printf("thread1 : number = %d\n",number);                  pthread_mutex_lock(&mut);                          number++;                  pthread_mutex_unlock(&mut);                  sleep(2);          }              printf("thread1 :主函数在等我完成任务吗?\n");          pthread_exit(NULL);  }    void *thread2()  {          printf("thread2 : I'm thread 2\n");            for (i = 0; i < MAX; i++)          {                  printf("thread2 : number = %d\n",number);                  pthread_mutex_lock(&mut);                          number++;                  pthread_mutex_unlock(&mut);                  sleep(3);          }              printf("thread2 :主函数在等我完成任务吗?\n");          pthread_exit(NULL);  }    void thread_create(void)  {          int temp;          memset(&thread, 0, sizeof(thread));          //comment1          /*创建线程*/          if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0)       //comment2                  printf("线程1创建失败!\n");          else                  printf("线程1被创建\n");            if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0)  //comment3                  printf("线程2创建失败");          else                  printf("线程2被创建\n");  }    void thread_wait(void)  {          /*等待线程结束*/          if(thread[0] !=0) {                   //comment4                  pthread_join(thread[0],NULL);                  printf("线程1已经结束\n");          }          if(thread[1] !=0) {                //comment5                  pthread_join(thread[1],NULL);                  printf("线程2已经结束\n");          }  }    int main()  {          /*用默认属性初始化互斥锁*/          pthread_mutex_init(&mut,NULL);            printf("我是主函数哦,我正在创建线程,呵呵\n");          thread_create();          printf("我是主函数哦,我正在等待线程完成任务阿,呵呵\n");          thread_wait();            return 0;  }    

下面我们先来编译、执行一下
引文:
falcon@falcon:~/program/c/code/ftp$ gcc -lpthread -o thread_example thread_example.c
falcon@falcon:~/program/c/code/ftp$ ./thread_example
我是主函数哦,我正在创建线程,呵呵
线程1被创建
线程2被创建
我是主函数哦,我正在等待线程完成任务阿,呵呵
thread1 : I'm thread 1
thread1 : number = 0
thread2 : I'm thread 2
thread2 : number = 1
thread1 : number = 2
thread2 : number = 3
thread1 : number = 4
thread2 : number = 5
thread1 : number = 6
thread1 : number = 7
thread2 : number = 8
thread1 : number = 9
thread2 : number = 10
thread1 :主函数在等我完成任务吗?
线程1已经结束
thread2 :主函数在等我完成任务吗?
线程2已经结束
实例代码里头的注释应该比较清楚了吧,下面我把网路上介绍上面涉及到的几个函数和变量给引用过来。
引文:
线程相关操作
一 pthread_t
pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
  typedef unsigned long int pthread_t;
  它是一个线程的标识符。
二 pthread_create
函数pthread_create用来创建一个线程,它的原型为:
  extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
  void *(*__start_routine) (void *), void *__arg));
  第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
三 pthread_join pthread_exit
  
函数pthread_join用来等待一个线程的结束。函数原型为:
  extern int pthread_join __P ((pthread_t __th, void **__thread_return));
  第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:
  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
  唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给 thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
  在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下面,我们来了解线程的一些常用属性以及如何设置这些属性。
互斥锁相关
互斥锁用来保证一段时间内只有一个线程在执行一段代码。
一 pthread_mutex_init
函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数 pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数 pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值, PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、 PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。
二 pthread_mutex_lock pthread_mutex_unlock pthread_delay_np
   pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。


注意:
1 需要说明的是,上面的两处sleep不光是为了演示的需要,也是为了让线程睡眠一段时间,让线程释放互斥锁,等待另一个线程使用此锁。下面的参考资料1里头说明了该问题。但是在linux下好像没有pthread_delay_np那个函数(我试了一下,提示没有定义该函数的引用),所以我用了sleep来代替,不过参考资料2中给出另一种方法,好像是通过pthread_cond_timedwait来代替,里头给出了一种实现的办法。
2 请千万要注意里头的注释comment1-5,那是我花了几个小时才找出的问题所在。
如果没有comment1和comment4,comment5,将导致在pthread_join的时候出现段错误,另外,上面的comment2和comment3是根源所在,所以千万要记得写全代码。因为上面的线程可能没有创建成功,导致下面不可能等到那个线程结束,而在用pthread_join的时候出现段错误(访问了未知的内存区)。另外,在使用memset的时候,需要包含string.h头文件哦
参考资料:
1。Linux下的多线程编程

2。pthread_delay_np(这里头有个关于posix条件变量的例子)

3。pthread_join和段错误(非常感谢这里头的哥们,千万要看哦)

4。posix线程编程指南[学习linux下多线程,不看这个你会后悔的]
http://www.linuxforum.net/forum/showflat.php?Cat=&Board=program&Number=294073&page=0&view=collapsed&sb=5&o=7&fpart=http://www.bczs.net/xml/2005/11/5/4374188.xmlhttp://bbs.chinaunix.net/archiver/?tid-584593.htmlhttp://linux.chinaunix.net/doc/program/2001-08-11/642.shtml

另一篇

 在进入实战篇以前,我们简单说一下多线程编程的一般原则。

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

  安全性的保证可以通过设计安全的类和程序员的手工控制。如果多个线程对同一对象访问不会危及安全性,这样的类就是线程安全的类,在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.参与者与容积比

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

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

[写在前面]

  随着计算机技术的发展,编程模型也越来越复杂多样化。但多线程编程模型是目前计算机系统架构的最终模型。随着CPU主频的不断攀升,X86架构的硬件已经成为瓶,在这种架构的CPU主频最高为4G。事实上目前3.6G主频的CPU已经接近了顶峰。

  如果不能从根本上更新当前CPU的架构(在很长一段时间内还不太可能),那么继续提高CPU性能的方法就是超线程CPU模式。那么,作业系统、应用程序要发挥CPU的最大性能,就是要改变到以多线程编程模型为主的并行处理系统和并发式应用程序。

  所以,掌握多线程编程模型,不仅是目前提高应用性能的手段,更是下一代编程模型的核心思想。多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O,OEMBIOS等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。

  [第一需要弄清的问题]

  如同程序和进程的区别,要掌握多线程编程,第一要弄清的问题是:线程对象和线程的区别。

  线程对象是可以产生线程的对象。比如在java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。

  鉴于作者的水平,无法用更确切的词汇来描述它们的定义。但这两个有本质区别的概念请初学者细细体会,随着介绍的深入和例程分析的增加,就会慢慢明白它们所代表的真实含义。

  天下难事必始于易,天下大事必始于细。

  让我们先从最简单的"单线程"来入手:(1)带引号说明只是相对而言的单线程,(2)基于java。

class BeginClass{          public static void main(String[] args){              for(int i="0";i<100;i++)                  System.out.println("Hello,World!");          }      }

  如果我们成功编译了该java文件,然后在命令行上敲入:

  java BeginClass

  现在发生了什么呢?每一个java程序员,从他开始学习java的第一分钟里都会接触到这个问

  题,但是,你知道它到底发生发什么?

  JVM进程被启动,在同一个JVM进程中,有且只有一个进程,就是它自己。然后在这个JVM环境中,所有程序的运行都是以线程来运行。JVM最先会产生一个主线程,由它来运行指定程序的入口点。在这个程序中,就是主线程从main方法开始运行。当main方法结束后,主线程运行完成。JVM进程也随之退出。

  我们看到的是一个主线程在运行main方法,这样的只有一个线程执行程序逻辑的流程我们称

  之为单线程。这是JVM提供给我们的单线程环境,事实上,JVM底层还至少有垃圾回收这样的后台线程以及其它非java线程,但这些线程对我们而言不可访问,我们只认为它是单线程的。

  主线程是JVM自己启动的,在这里它不是从线程对象产生的。在这个线程中,它运行了main方法这个指令序列。理解它,但它没有更多可以研究的内容。

  [接触多线程]

class MyThread extends Thread{          public void run(){              System.out.println("Thread say:Hello,World!");          }      }        public class MoreThreads{          public static void main(String[] args){              new MyThread();              new MyThread().start();              System.out.println("Main say:Hello,World");          }      }

  执行这个程序,main方法第一行产生了一个线程对象,但并没有线程启动。

  main方法第二行产生了一个线程对象,并启动了一个线程。

  main方法第三行,产生并启动一个线程后,主线程自己也继续执行其它语句。

  我们先不研究Thread对象的具体内容,稍微来回想一下上面的两个概念,线程对象和线程。在JAVA中,线程对象是JVM产生的一个普通的Object子类。而线程是CPU分配给这个对象的一个运行过程。我们说的这个线程在干什么,不是说一个线程对象在干什么,而是这个运行过程在干什么。如果一时想不明白,不要急,但你要记得它们不是一回事就行了。

  累了吧?为不么不继续了?

  基于这种风格来介绍多线程,并不是每个人都喜欢和接受的,如果你不喜欢,正好不浪费你的时间了,而如果你接受的话,那就看下一节吧。

 在进入实战篇以前,我们简单说一下多线程编程的一般原则。

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

  安全性的保证可以通过设计安全的类和程序员的手工控制。如果多个线程对同一对象访问不会危及安全性,这样的类就是线程安全的类,在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.参与者与容积比

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

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

转载自dev2dev网友axman的go deep into java专栏。

http://man.lupaworld.com/content/develop/joyfire/system/11.html#I263  自己找的 挺好的