多線程編程 實戰篇 (一)

實戰篇(一)

在進入實戰篇以前,我們簡單說一下多線程編程的一般原則.

[安全性]是多線程編程的首要原則,如果兩個以上的線程訪問同一對象時,一個線程會損壞
另一個線程的數據,這就是違反了安全性原則,這樣的程序是不能進入實際應用的.

安全性的保證可以通過設計安全的類和程序員的手工控制.如果多個線程對同一對象訪問不
會危及安全性,這樣的類就是線程安全的類,在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.參與者與容積比.
    對於生產者和消費者的比例,以及桌子所能放置最多菜的數量三者之間的關係

是影響性能的重要因素,如果是過多的生產者在等待,則要增加消費者或減少生產者的數據,反之
則增加生產者或減少消費者的數量.
另外如果桌子有足夠的容量可以很大程序提升性能,這種情況下可以同時提高生產者和
消費者的數量,但足夠大的容時往往你要有足夠大的物理內存.

原文作者:axman
原文出處:http://blog.csdn.net/axman/article/details/431801
個人覺得此博主寫得文章非常好,推薦大家翻閱。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章