KODAMA LAB
Home
Lecture notes

Object-Oriented and Concurrent Programming

From the origins of object orientation in Simula67, through inheritance in Java and its pitfalls, to threads, semaphores and the bounded-buffer problem — with worked code and exercises.

18 sections 46 code listings 3 exercises

→ JavaScript edition → Python edition

01オブジェクト指向とは

オブジェクト指向が考えだされた,プログラミング言語 Simula67 は, シミュレーション言語であった(1967 年に発表されたので 67 がついている). 一般にシミュレーション言語とは,自然現象,人工的現象などを,コンピュー タ上で,模擬的に実行させ実際のデータを知ることが難しい場合に,そのデー タを予測するための言語である.例えば「地下鉄の駅を作る場合に,どこに階 段やエスカレータを設置したらよいか,を決める時に,実際の人の乗降者 数を入力し,人の流れを模擬実行させてみる」など,いろいろなことが考えられ る.

シミュレーション言語の使命は,

物理現象をどのようにプログラミング言語で表現するか
である.
そのため Simula67 では,全てを もの(object, オブジェクト) として捕え,その「もの」と「もの」との 干渉(interaction) で現象が進んで行 くと考えた.その干渉のことを メッセージの受渡し(message passing) という1. 正確には Simula67 では,この「もの」に相当する概念をプログラミング言 語のデータに近い「抽象データ型」と表現していた.その後の アーラン・ケ イ(米国アップルコンピュータ社,開発当時は,米国ゼロックス社) より 1970 年代に Smalltalk の開発が進み,1980 年に発表された Smalltalk-80 より「オブジェクト指向」の概念が確立された. その Smalltalk-80 は「オブジェクト指向」の概念を実際にコンピュータ上で表 現することができた最初のプログラミング言語である.

オブジェクト指向言語の基本は,その意味で「オブジェクト」をどう定義する か,である.

オブジェクトは,以下の 3 つの要素から成ると考える.

  • 状態
  • 状態を変更するプログラム(命令の集まり)
  • 計算を実行する主体
状態(state) は,オブジェクト固有の値で,文字どおりオブジェクトの状 態を示すものである.「状態を実行するプログラム」は,通常 メソッド(method) といい,オブジェクトの状態を変更し,その目的のために,他のオブジェクト とのメッセージの受渡しを処理したりするプログラムである. 「計算を実行する主体」は,コンピュータそのもの,つまり CPU を指す.各 オブジェクトは,同じ計算機で実行することは仮定していない,いわんや,コ ンピュータ上で実行するとも仮定していないので,その計算主体をオブジェク ト自身が持つ(勝手に定義してよい)と考える. そのため,オブジェクト指向では,各オブジェクトが「並列に」処理または, 動作することが可能であることが前提であり,全く独立に処理が進むことを前 提としている2. ここで,オブジェクトを構成する上の 3 つは,オブジェクトにより カプ セル化(encapsulation) されたという.この特徴により,オブジェクトに固 有な状態など(メソッド自体を変更する3ことも考えられる)を変更するには,オブジェクトに直接メッ セージの受渡しをしなければならない.
図 1: オブジェクト
オブジェクト 状態 オブジェクト固有の値 メソッド 状態を変更するプログラム 計算を実行する主体 各オブジェクトが自分で持つ = 並列に動くことが前提 …
オブジェクトを構成する 3 つの要素

オブジェクトは,以上 3 つからのみ成り立ち, 各オブジェクト間のメッセージの受渡しの 通信規約 (protocol, プロトコル) を別途定めることにより,全体として現象が進む と考えることができる.例えば,メッセージの受渡しには,

  • 同期通信
  • 非同期通信
が考えられるが,この違いを明確にしたオブジェクト指向に関する研究も盛ん に行われている.

例えば,簡単な物理現象を「オブジェクト指向」で表現してみよう. 登場するオブジェクトは,

  • 人間
  • 飛行機
  • 木
を考える.「飛行機が空を飛ぶと,風が発生し木が揺れる.それを人間が見 ている」という現象をオブジェクト指向で表現してみよう.
簡単な物理現象
「オブジェクト飛行機」が飛び,「メッセージ風」を「オブジェクト木」に受 け渡し,結果「オブジェクト木」が揺れる.「オブジェクト人間」は, 「オブジェクト飛行機」にメッセージを受渡し,その状態(どのくらいの速度で 飛行しているか,何色かなど)を知ることができる.更に,「オブジェクト木」 にメッセージを受渡し,その状態を知ることができる. 次節では,以上の物理現象をプログラミングするための言語を定義してみよう.
図 2: 簡単な物理現象

このように,オブジェクト指向とは,一般の現象をより自然に捕らえ,オブジェ クトとメッセージという簡単な枠組みに当てはめる点に主眼がおかれている.


平成12年8月9日

02オブジェクト指向言語

オブジェクト指向言語とは,上の「オブジェクト指向」概念に基いたプログラ ミング言語である.つまり,「オブジェクト指向」を基に設計し,コンピュー タ上で実行することを目的に,より人間の思考に近い形で表現することを可能 にした言語といってもよい.現在では様々なオブジェクト指向言語が開発され ているが,各言語では「オブジェクト指向」を独自に拡張している.

一般にオブジェクト指向言語は2 つに分類することができる.

  1. オブジェクトベース
  2. クラスベース
1.オブジェクトベース言語では,オブジェクトを直接的に定義する言語である. より元来のオブジェクト指向に近いと考えてよい.オブジェクト指向言語 Self などがある. また,「プロトタイプベース」ということもある. 2. クラスベース言語は,オブジェクトの雛形として クラス(class)を定 義し,各オブジェクトはクラスからの インスタンシエーション (instantiation,実体化という)により,動的に生成する.これにより同じ性 質をもった複数のオブジェクトを生成することができる.最初に(商用でオブジェクト指向言語として)提案された Smalltalk-80 がこの「クラスベース」であったため,クラスがオブジェクト 指向の典型的な要素となったが,必ずしもクラスを含む必要はない.

現在では,代表的なオブジェクト指向として Java があり「クラスベース」 であるので本書では,クラスベースについて説明しよう(Java によるプログラ ミングは次節以降).

では,仮想的なクラスベース言語で, 上の物理現象を表現する.ここで,各クラスは class-名前-本体- end の本体の部分に定義し,状態は state, メソッドは method-名前-引数-本体- end と定義するものとする.

 class  Airplane
     state
                    速度,色など飛行機を表現する状態.
                 method 色を教えてください. is
                                 return 色  end
                 method 速度を教えてください. is
                                 return 速度  end
                 method 飛べ. is
                                飛行機が離陸し,飛行速度が定常状態で飛行.
                                 send 風.  to 木  end
 end
 class  Tree
                 state
                                揺れているか揺れていないかの状態.
                 method 風.  is
                                木を揺らす  end
                 method 風なし.  is
                                木を揺らさない  end
                 method 状態を教えてください.  is
                                 return 状態  end
 end
 class  Person
                 method 見る.  is
                                色 :=send 色を教えてください.  to 飛行機.
                                速度 :=send 速度を教えてください.  to 飛行機.
                                木の状態 :=send 状態を教えてください.  to 木.
                                色,速度,木の状態を表示する  end
 end
ここで,飛行機
Airplane,木
Tree, 人間
Person であることに注意しよう. Airplane, Tree と Person は,クラスであり, 各オブジェクト(飛行機,木,人間) はこのクラスから生成しなければならな い.また,上の記述では,「クラスを定義しただけ」であり,通常,全体の実 行を司るプログラムを書かなければならない.その書き方は,各プログラミン グ言語により異なるので,本書では次節以降で Java について説明する.


平成12年8月9日

03オブジェクト指向言語 Java

現在,代表的なオブジェクト指向言語 Java は,ゴスリン(米国サン・マイク ロシステムズ社) によって提案された言語である.ゴスリンによって研究され ていた当時は Oak といわれていた.

オブジェクト指向言語 Java は,「クラスベース」であるためクラスを定義す ることにより,プログラミングする.最初に,オブジェクト指向言語で説明し た例題を Java により定義してみよう.「オブジェクト指向」とは,最初に説 明したように「概念」であるため,前節で定義した抽象化されたクラスを プログラミングの際に「具体化」する必要がある.この具体化は,各プログラマの 責任である.そのため,以下で定義するプログラムは,筆者の解 釈 であると考 えてよい4.

public class Person {
  private int AirplaneColor,AirplaneSpeed;
  private boolean TreeStatus;
  private Airplane knownAirplane;
  private Tree knownTree;
  Person(Tree t,Airplane a) {
    knownTree = t; knownAirplane = a;
  }
  public void Seeing() {
    TreeStatus=knownTree.WhatIsYourStatus();
    AirplaneSpeed=
      knownAirplane.WhatIsYourSpeed();
    AirplaneColor=
      knownAirplane.WhatIsYourColor();
    System.out.println(
      "TStatus: "+TreeStatus+
      ",ASpeed: "+AirplaneSpeed+",AColor: "+
      Color.toString(AirplaneColor));
  }
  public static void main(String args[]) {
    Tree t = new Tree();
    Airplane a = new Airplane(t);
    Person p = new Person(t,a);
    for(int i=0;i<21;i++) {
      for(int j=0;j<10000;j++){}      
      p.Seeing(); a.Flying();
    }
    p.Seeing();
  }
}
class Color {
  static int White=0,Red=1,Green=2,Blue=4;
  static String toString(int Col) {
    switch(Col) {
      case 0: return "White";
      case 2: return "Read";
      case 4: return "Green";
      case 8: return "Blue";
      default: return "unknownColor";
    }
  }
}
class Airplane {
  private int Speed,Col = Color.White;
  private Tree knownTree;
  private boolean Upper=true;
  Airplane(Tree t) {
    knownTree = t;
  }
  public void Flying() {
    if(Upper==true) {
      if(Speed++==9) Upper=false;
    } else
      if(--Speed<=0) Speed=0;
    if(Speed>0) knownTree.Wind();
    else knownTree.stopWind();
  }
  public int WhatIsYourSpeed() {
    return Speed;
  }
  public int WhatIsYourColor() {
    return Col;
  }
}
class Tree {
  private boolean Status = false;
  public boolean WhatIsYourStatus() {
    return Status;
  }
  public void Wind() {
    Status = true;
  }
  public void stopWind() {
    Status = false;
  }
}
このプログラムを実行すると以下のようになる.
TStatus: false,ASpeed: 0,AColor: White
TStatus: true,ASpeed: 1,AColor: White
TStatus: true,ASpeed: 2,AColor: White
TStatus: true,ASpeed: 3,AColor: White
TStatus: true,ASpeed: 4,AColor: White
TStatus: true,ASpeed: 5,AColor: White
TStatus: true,ASpeed: 6,AColor: White
TStatus: true,ASpeed: 7,AColor: White
TStatus: true,ASpeed: 8,AColor: White
TStatus: true,ASpeed: 9,AColor: White
TStatus: true,ASpeed: 10,AColor: White
TStatus: true,ASpeed: 9,AColor: White
TStatus: true,ASpeed: 8,AColor: White
TStatus: true,ASpeed: 7,AColor: White
TStatus: true,ASpeed: 6,AColor: White
TStatus: true,ASpeed: 5,AColor: White
TStatus: true,ASpeed: 4,AColor: White
TStatus: true,ASpeed: 3,AColor: White
TStatus: true,ASpeed: 2,AColor: White
TStatus: true,ASpeed: 1,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White

では,順次説明してみよう.Java では,前節で説明した「メッセージの受渡 し」は,メソッド呼出しとなる.クラスより生成されたインスタンス(オブ ジェクト) のメソッドを通常の手続き呼出しのように呼出す.例えば, クラス Airplane のメソッド Flying() の定義の中での,

knownTree.Wind();
は,
この飛行機が知っている木に Wind()(風の意味) というメッセージを受渡したことと同じである.

前節に無かった定義では,各クラスは コンストラクタ(constructor) という部分を持っている.例えば,クラ ス Person では,

Person(Tree t,Airplane a) {
  knownTree = t; knownAirplane = a;
}
となっている.
このコンストラクタは, new によりクラス Person からインスタンス(オブジェクト)を生成する場合に,一度だけ 呼出される部分である.ここでは,クラス Person からインスタンス が生成(いわゆる人間が一人)される時,そのインスタンスへ,(見るべき)木と 飛行機を教えている.

各クラスの状態と,メソッドの宣言は,以下のようになっている.

クラス 状態 メソッド
Person AirPlaneColor Seeing()
AirPlaneSpeed
TreeStatus
knownTree
Airplane Speed Flying()
Col WhatIsYourSpeed()
knownTree WhatIsYourColor()
Upper
Tree Status WhatIsYourStatus()
Wind()
stopWind()

全体を統括するメソッド main は,クラス Person の中で定義されているが,特別な意味を持つので (特に Person の中でなくてもよい), static として定義する.

  public static void main(String args[]) {
    Tree t = new Tree();
    Airplane a = new Airplane(t);
    Person p = new Person(t,a);
    for(int i=0;i<21;i++) {
      for(int j=0;j<10000;j++){}
      p.Seeing(); a.Flying();
    }
    p.Seeing();
  }
これより,全体の処理の流れは,
  1. クラス Tree よりインスタンス tを生成する.
  2. クラス Airplane よりインスタンス aを生成する.
  3. クラス Person よりインスタンス pを生成する.
  4. 以下の処理を 21 回繰り返す.この間に飛行機は速度を増し,やがて減 速し,止まる.
    1. 数秒待つ.
    2. 人間 pにメッセージ「見る」を受渡す.
    3. 飛行機 aにメッセージ「飛べ」を受渡す.
  5. 最後にもう一度,人間 p にメッセージ「見る」を受渡す.

Java では,前に定義した仮想的な言語と異なり「実際に実行させる」必要が あるので,このように「全体を統括する」プログラムが必要となる. しかし,他の「クラス定義」の部分は,全く同様に定義することができた.

継承

クラスベースのオブジェクト指向言語にとって重要な性質として 継承 (inheritance) がある.各オブジェクトが持つ「状態」は,そのオブジェクト が振る舞う挙動(メソッド)を司る重要な「性質」を表現している. 「性質」とは,言い換えれば「状態」と「メソッド」から表現され ると考えることができる.

そのため,既に存在するオブジェクトの「性質」を再利用することが,有効か つ自然な表現であることが考えられる.その再利用のことを 継承 (inheritance) という. 「クラスベース」のオブジェクト指向言語の場合は,雛形であるクラスにより その「性質」を表現しているので クラス継承(class inheritance) とも いう.

例えば,上で定義したクラス Airplane を外から制御可能な飛行機 としてみよう.

 class  ControledAirplane  inherits Airplane
     method スピードを増せ. is
                    スピードを増す.  end
                 method スピードを落とせ. is
                                スピードを落とす.  end
 end
inherits の次に示した名前( Airplane) より親クラスを示す. 親クラスに対して,子クラスのことを サブクラス(sub-class) という. サブクラスで定義した状態,メソッド以外の状態,メソッドは全て, 親クラスのそれを使う(継承する). このように,任意のクラスの子クラスを作り,状態,メソッドを継承すること ができる.

では Java で書いてみよう.しかし簡単には定義することはできない. まず,仮想的に定義したクラス同様にクラス ControledAirplane を定義すると以下のようになる.

class ControledAirplane extends Airplane {
  ControledAirplane(Tree t) {
    super(t);
  }
  public void Increment() {
    Speed++;
  }
  public void Decrement() {
    Speed--;
  }
}
よって,
クラスの状態とメソッドは,以下のようになる.
クラス 状態 メソッド
Controled Speed Flying()
Airplane Col WhatIsYourSpeed()
knownTree WhatIsYourColor()
Upper Increment()
Decrement()
このクラスも以下のように,メソッド main の中で生成することができ るので,コンストラクタを定義する必要がある.このクラスでは,親クラス (Java では superで示す)のコンストラクタを呼び出している.
ControledAirplane a = new ControledAirplane(t);
更に,このクラス定義では親クラスの変数 Speed を更新しているが, この変数宣言も private から protected に変更する必要がある. このように,継承される可能性のある変数は protected または public で宣言する必要がある.ここでは,クラス Airplane 内の変数 Speed を protected で定義しよう.
protected int Speed;

結果として, ControledAirplane のインスタンスを生成するので, 以下のように挙動を変更してみよう.

for(int i=0;i<21;i++) {
  for(int j=0;j<10000;j++){}      
    p.Seeing(); a.Flying();
    if(i==4) a.Increment();
  }
その結果,以下のような実行結果となる.
TStatus: false,ASpeed: 0,AColor: White
TStatus: true,ASpeed: 1,AColor: White
TStatus: true,ASpeed: 2,AColor: White
TStatus: true,ASpeed: 3,AColor: White
TStatus: true,ASpeed: 4,AColor: White
TStatus: true,ASpeed: 6,AColor: White
TStatus: true,ASpeed: 7,AColor: White
TStatus: true,ASpeed: 8,AColor: White
TStatus: true,ASpeed: 9,AColor: White
TStatus: true,ASpeed: 10,AColor: White
TStatus: true,ASpeed: 9,AColor: White
TStatus: true,ASpeed: 8,AColor: White
TStatus: true,ASpeed: 7,AColor: White
TStatus: true,ASpeed: 6,AColor: White
TStatus: true,ASpeed: 5,AColor: White
TStatus: true,ASpeed: 4,AColor: White
TStatus: true,ASpeed: 3,AColor: White
TStatus: true,ASpeed: 2,AColor: White
TStatus: true,ASpeed: 1,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White
ASpeed が 4 より 6 になっていることがわ かる.

多重定義

同一クラスにおいて,同じ名前のメソッドを複数定義する場合,そのメソッドは 多重定義(overloading) されたという. 多重定義されたメソッドの区別は,その引数(の型) で行う. 例えば以下のプログラムを考える.
public class Operator {
  public boolean Plus(boolean A,boolean B) {
    return (A || B);
  }
  public int Plus(int A,int B) {
    return A+B;
  }
  public double Plus(double A,double B) {
    return A+B;
  }
  public String Plus(String A,String B) {
    return A+B;
  }
  public static void main(String args[]) {
    Operator a = new Operator();
    System.out.println("true+false="+
      (a.Plus(true,false)));
    System.out.println("1+2="+(a.Plus(1,2)));
    System.out.println("1.2+2.3="+
      (a.Plus(1.2,2.3)));
    System.out.println("\"I am \"+\"a student\"="+
      (a.Plus("I am ","a student")));
  }
}
このプログラムを実行すると,以下のようになる.
true+false=true
1+2=3
1.2+2.3=3.5
"I am "+"a student"=I am a student
クラス Operator の中にはメソッド Plus が複数定義されている ことがわかる.型 boolean, int, double, Sring に対して Plus が定義されている.メソッド main では, メソッドを全て同じように a.Plus と呼出しているが, 引数の型に応じて呼出されるメソッドが異なっていることがわかる.

メソッド名は,演算子と捕らえることもできる.上の例の Plus がその 例である.一般に演算子は,型に左右されず複数の型に対して有効なことが多 いので,「常識」により近いという意味でも演算子を多重定義するとわか りやすい.Java ではできないが,C++ では「演算子の多重定義」が 提供されている.

平成12年8月9日

オーバーライド

継承の応用として,親クラスと同じメソッド名 (以下 A としよう) を定 義することを考えることができる.このメソッドは,親の同じ名前のメソッド を オーバーライド(override) した,という. オーバーライドとは,継承と同じように定義するが,意味的に逆に,親のメ ソッドの機能を否定している点に注意しよう. 例えば以下のプログラムを考える.
public class A {
  public void A1() {
    System.out.println("calling A1 of A.");
  }
  public void A2() {
    System.out.println("calling A2 of A.");
  }
  public static void main(String args[]) {
    A a = new A(); B b = new B();
    a.A1(); b.A1(); b.A2();
  }
}
class B extends A {
  public void A1() {
    System.out.println("calling A1 of B.");
  }
}
このプログラムを実行すると,以下のようになる.
calling A1 of A.
calling A1 of B.
calling A2 of A.
一行目は, a.A1() に相当する処理で,クラス A より生成したオ ブジェクト a のメソッドを呼出し, 二行目は, b.A1() に相当する処理で,クラス B より生成したオ ブジェクト b でオーバーライドしたメソッドを呼出している. 最後の行では, b.A2() に相当する処理で,クラス B が 親クラス A で定義されたメソッド A2 を継承したことを示して いる.

「継承」さらに,ここで定義した「オーバーライド」を多用すればより複雑な関係な どを記述することができるが,慣れないとどのような処理をするかわからな くなるので注意が必要である.例えば,上の例で main メソッドでの オブジェクト生成で,

A a = new B();
と宣言することも可能である.
クラス A のサブクラス B より生 成されたインスタンスは,クラス A の性質も持つためクラス A のイン スタンス(Java では 型 A) としても認識することが可能である. この性質は,以下の命題が成立する.
命題1
クラス
$A$
のインスタンス
$a$
を
$a:A$
と書き,クラス
$A$
,
$B$
に対し て,クラス
$B$
はクラス
$A$
のサブクラスである時,
と書くこと にする.この時
$b:B$
かつ
であるならば
$b:A$
である.

この性質を サブシューム(subsume) という.また,クラス

$B$
から生 成されたインスタンス
$b$
は,クラス
$A$
に「サブシュームされた」という. この性質は,Java のような 「クラス=型」 とするオブジェ クト指向言語で成立する性質である5

さて,この性質を使って,

A a = new B();
このように書き換えた場合,
a.A1();
は,クラス A で定義された
メソッド A1 を呼出すのであろうか. それとも,クラス B で定義されたメソッド A1 を呼出すで あろうか.前者は,メソッドが 静的に束縛された(statically binded) オブジェクト指向言語で採用される性質であり,後者は,メソッドが 動的に束縛された(dynamically binded) オブジェクト指向言語で採用 される性質である.一般には,どちらともいえないが Java はどちらの性質も もつであろうか.読者の課題にしたい6.
平成12年8月9日

Try it: which method actually runs

Toggle whether each class defines draw(), then choose the runtime type.

defines draw()
Shape ← 実行
Polygon ← 実行
Square ← 実行
runtime type

継承の問題点 (1)

以上で紹介した「継承」,「多重定義」,「オーバーライド」,「サブシュー ム」の各性質を多用することにより,より複雑なクラス間の関係を定義するこ とができる. しかし,どのように実行されるかわからない場合など,難しい問題もある. この節と,次節では,それを紹介しよう.

そもそも,「クラス=型」 にした理由は, プログラムのエラーを 「静的に」(コンパイル時) に見つけるための型 を,クラスと同等に扱うことにより,クラス間の関係の意味も含んだ型として,静的にエラーを発見しようとするためであった.しかし,「多重定義」,「オーバーライド」,「サブシュー ム」の性質などに より,静的にエラーのチェックをすることが不可能であることがわかってきた. その例を紹介しよう.

まず,サブシュームの性質は,上で紹介した代入演算の他に,オブジェクトを メソッドの引数で受渡した場合にも生ずる.

public class C {
  public void C1() {
    System.out.println("calling C1 of C.");
  }
  public void C2() {
    System.out.println("calling C2 of C.");
  }
  public static void main(String args[]) {
    E e = new E();
    e.E1(new D());
  }
}
class D extends C {
  public void C1() {
    System.out.println("calling C1 of D.");
  }
  public void D1() {
    System.out.println("calling D1 of D.");
  }
}
class E {
  public void E1(C c) {
    c.C1();
  }
}
このプログラムを実行すると以下のようになる.
calling C1 of D.
main メソッドの中で,
クラス E のインスタンスのメソッド E1 にクラス D のインスタンスを受渡している.
e.E1(new D());
しかし,受け口であるクラス E の定義では,以下のようになっている.
public void E1(C c) {
  c.C1();
}
クラス C のインスタンスを受付けるようになっている.これは,クラス D がクラス C のサブクラスであるため サブシュームによりクラス D のインスタンスを受け付けることができ るためである.

つまり,オブジェクトをメソッドの引数として受渡す場合は,対応するク ラスのサブクラスのどれかが受渡される可能性がある.例えば,上の例では,実際にはクラス D のインスタンスを受渡しているので,

public void E1(C c) {
  c.D1();
}
とすることも可能であると考えるが,
このように書いたのであれば,「静的なチェック」により, クラス C のメソッドには D1 がないことがわかり, エラーとなってしまう.そのため Java では,
public void E1(C c) {
  D d=(D)c;
  d.D1();
}
のように キャスト(cast) を使うことにより,
実際のメソッドを呼出すことができる.

これで,めでたく実際のメソッドを呼出すことが可能となったわけであるが, キャストは,非常に危うい性質を持っている.例えば,別のオブジェクトが,

e.E1(new C());
と呼出したらどうなるであろう.
これは,クラス C のインスタンスに は,メソッド D1 が存在しないのでエラーとなってしまう.

最後に,このような場合の最もエレガントなプログラミングを紹介しよう.

public void E1(C c) {
  if(c instanceof D) {D d=(D)c; d.D1();}
  else if(c instanceof C) c.C1();
}
このように, instanceof 演算子を
使うことにより,どのようなサブクラスかを「動的に」判断して適切な処理を 行うことが必要である.

一般に,「クラス=型」 であるオブジェクト指向言語でも, メソッドの引数として,オブジェクトを受渡す場合には,ちょうどそのクラス (型) のオブジェクトだけでなく,サブクラスのどれかが受渡される可能性がある.そのためには,本来型という「静的にチェックできるはず」の機能も, その範疇ではチェックしきれないことが知られている. そのために,Java では「動的に」サブクラス関係をチェックする instanceof という演算子が提供されている.また,他の同様のオブジェクト 指向言語でも同様の演算子が提供されている7プログラマは,どこで動的にチェックする必要があるかを常に,意識しなけれ ばならない.

平成12年8月9日

継承の問題点 (2)

最近のオブジェクト指向の研究では,
クラス≠型

が常識である.それは「クラス=型」にすると以下のような問 題が知られているからである.Java で示そう.
public class Food {
  private String name;
  Food(String f) { name = f; }
  public static void main(String args[]) {
    Person p = new Person();
    Vegetarian v = new Vegetarian();
    Vegetable v1 = new Vegetable("Cabbage");
    Food f1 = new Food("Meat");
    System.out.println(
      "Can a person eat cabbages? "+p.Eat(v1));
    System.out.println(
      "Can a person eat meats? "+p.Eat(f1));
    System.out.println(
      "Can a vegetarian eat cabbages? "+
      v.Eat(v1));
    System.out.println(
      "Can a vegetarian eat meats? "+v.Eat(f1));
  }
}
class Vegetable extends Food {
  String name;
  Vegetable(String s) { super(s); name = s; }
}
class Person {
  public boolean Eat(Food f) { return true; }
}
class Vegetarian extends Person {
  public boolean Eat(Vegetable v){return true;}
}
このプログラムでは,
人間( Person) と菜食主義者( Vegetarian)
, 食べ物( Food) と野菜( Vegetable)
を定義した. 各々「食べ物」は,野菜,肉すべての食べ物を含んでいるので,肉(meats) を 定義し,「野菜」は,キャベツ(cabbages) を定義した. その結果,人間,菜食主義者が各々「肉」を食べることができるか, 「野菜」を食べることができるか.を問い合わせてみた。 このプログラムを実行すると,以下のようになる.
Can a person eat cabbages? true
Can a person eat meats? true
Can a vegetarian eat cabbages? true
Can a vegetarian eat meats? true
このように「菜食主義者」(vegetarian) が,「肉を食べることが出来てしま う」(最後の行) が問題である.この原因として,「菜食主義者」は,「人間」の サブクラスであるので, Eat(Vegetable v) に当てはまらない場合は, 人間の Eat(Food f) を継承してしまうためである. そもそも,「菜食主義者」は,「人間」のほとんどの性質を継承するが, 「野菜以外の食べ物は食べない」ことを表現するために,オーバーライドを利 用するが,うまく表現できない.

では,例えば,

class Vegetarian {
  public boolean Eat(Vegetable v){return true;}
}
とすれば,
そさそうであるが,菜食主義者といえども人間であるので その性質を受け継ぐ関係は必要である.

一般に,このような時は(Java では書けないが), TypeOperator を使う ことが知られている.結果として,人間と菜食主義者のサブタイプ関係は成り 立つ.

TypeOperator Person[F<:Food] {
  public boolean Eat(F f) { return false; }
}
TypeOperator Vegetarian[V<:Vegetable] {
  public boolean Eat(V v) { return ture; }
}
として,
ある Vegetable のインスタンス v に対 して
が成立するようにすることができる. v の代わりに,すべての Food のインスタンス f では 成立しない.

結果として

であるが,逆は成立しない関係である.ここで,クラス
$A$
,
$B$
のインスタン スを
$a$
,
$b$
とし,更に,その型を
,
と書く.
$b$
の型が
$a$
の型のサブタイプである時
とする.

平成12年8月9日

並列オブジェクト指向

オブジェクト指向は,本来はその記述言語である「オブジェクト指向言語」と は区別し「概念」であった.そして,いかに実際の現象を「うまく」表現する かがその意義である「シミュレーション」をその祖先としている.そのように 考えると,各オブジェクトは,「並列に」動作することが前提でモデル化され ている場合が多い.そのようなモデルを 並列オブジェクト指向モデル(parallel object-oriented model) という.

並列オブジェクト指向の起源ともいえるモデルとして アクターモデル (actor) がある.そのモデルは,MIT のヒューイットにより考え出された概念で, 数学的なモデルで定義した最初である.このモデルが考えだされた 1970 年代 には,まだオブジェクトの概念もまとまっていなかった時代でオブジェクトに 相当するものは アクター(actor) といった. その後,さらに発展した概念として ABCM(An oBject oriented Concurrent Model) が提案された.このモデルは東大の米澤により考え出された. このモデルも同様に数学的モデルとして定義されている.

最後に,上で紹介した例題で各オブジェクトをスレッドとしてみよう.

public class Person extends Thread {
  private int AirplaneColor,AirplaneSpeed;
  private boolean TreeStatus;
  private Airplane knownAirplane;
  private Tree knownTree;
  Person(Tree t,Airplane a) {
    knownTree = t; knownAirplane = a;
  }
  public void run() {
    for(int i=0; i<21;i++) {
      try {
        sleep(1);
      } catch (Exception e) {}
      TreeStatus=knownTree.WhatIsYourStatus();
      AirplaneSpeed=
        knownAirplane.WhatIsYourSpeed();
      AirplaneColor=
        knownAirplane.WhatIsYourColor();
      System.out.println(
        "TStatus: "+TreeStatus+
        ",ASpeed: "+AirplaneSpeed+",AColor: "+
        Color.toString(AirplaneColor));
    }
  }
  public static void main(String args[]) {
    Tree t = new Tree();
    Airplane a = new Airplane(t);
    Person p = new Person(t,a);
    a.start(); p.start();
  }
}
class Color {
  static int White=0,Red=1,Green=2,Blue=4;
  static String toString(int Col) {
    switch(Col) {
      case 0: return "White";
      case 2: return "Read";
      case 4: return "Green";
      case 8: return "Blue";
      default: return "unknownColor";
    }
  }
}
class Airplane extends Thread {
  protected int Speed;
  private int Col = Color.White;
  private Tree knownTree;
  private boolean Upper=true;
  Airplane(Tree t) {
    knownTree = t;
  }
  public void run() {
    for(int i=0;i<21;i++) {
      try {
        sleep(1);
      } catch (Exception e) {}
      if(Upper==true) {
        if(Speed++==9) Upper=false;
      } else
        if(--Speed<=0) Speed=0;
      if(Speed>0) knownTree.Wind();
      else knownTree.stopWind();
    }
  }
  public int WhatIsYourSpeed() {
    return Speed;
  }
  public int WhatIsYourColor() {
    return Col;
  }
}
class Tree {
  private boolean Status = false;
  public boolean WhatIsYourStatus() {
    return Status;
  }
  public void Wind() {
    Status = true;
  }
  public void stopWind() {
    Status = false;
  }
}
ここでは,クラス Person のインスタンスと, クラス Airplane のインスタンスをスレッドとし 起動している.そのため Person のインスタンスと Airplane のインスタンスは独立に動作することになった. このプログラムを実行すると,以下のようになる.
TStatus: true,ASpeed: 1,AColor: White
TStatus: true,ASpeed: 7,AColor: White
TStatus: true,ASpeed: 8,AColor: White
TStatus: true,ASpeed: 9,AColor: White
TStatus: true,ASpeed: 10,AColor: White
TStatus: true,ASpeed: 9,AColor: White
TStatus: true,ASpeed: 8,AColor: White
TStatus: true,ASpeed: 7,AColor: White
TStatus: true,ASpeed: 6,AColor: White
TStatus: true,ASpeed: 4,AColor: White
TStatus: true,ASpeed: 3,AColor: White
TStatus: true,ASpeed: 2,AColor: White
TStatus: true,ASpeed: 2,AColor: White
TStatus: true,ASpeed: 1,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White
TStatus: false,ASpeed: 0,AColor: White
この結果からわかるように,飛行機のスピードが,スピードアップの時に, より早くアップしているようにみることができるが,実際にはそうではない (更に,実行する毎に実行結果は異なる). これは,並列オブジェクト Airplane と 並列オブジェクト Person の同期がとれていないためである.しかし,よく考えてみる とそのように,人間が飛行機のスピードを把握する場合,そのスピードが一定 にアップする毎に知ることができるとは限らない,方が常である.よって, 並列オブジェクトモデルが,実際の現象を忠実に反映していることがわかる.

実際のプログラミングで,並列オブジェクトを単に「スレッド」で定義しただ けではその同期問題を別に解決しなければならないことが明らかになった. しかし,提案された並列オブジェクトモデルでは, 各異なった同期処理を提供している.読者の課題としよう.


平成12年8月9日

04並行プログラミング

オブジェクト指向言語 Java には,言語仕様として並行処理 (concurrent processing)の機能が備わっている. 一般に,同時にいつくかの処理をする時の処理単位を プロセス (process)または, タスク(task)という.どちらも,もう「古い」言葉 となってしまったが,プロセスは,主にミニコンピュータ用の OS で使われた言葉 (UNIX がその代表)で,タスクは,汎用コンピュータ(メインフレーム)用の OS がタイムシェアリングシステムを採用した場合の言葉である. 最近になって,UNIX の1 プロセスの中で,また,それに相当する処理単位 を実現したのが スレッド(thread)の始まりである.スレッドは,UNIX にあったプロセスの効率の悪さを克服するために,その 発生個数の多さ,処理効率の良さを目指している. 現在では,UNIX に限らず多くの OS(Win95,Win98 など) でスレッドを生成す ることができる.

プロセス,タスク,スレッドは論理的には同じ意味である.


平成12年8月9日

05プリエンプティブスレッド

以下のプログラムを考えてみよう
(ファイル名 Data.java).
public class Data {
  private int value;
  public int read() {
    return value;
  }
  public void write(int data) {
    this.value = data;
  }
  public static void main(String args[])
  {
    Data b = new Data();
    Producer p = new Producer(b);
    Consumer c = new Consumer(b);
    p.start(); c.start();
  }
}
class Producer extends Thread {
  private Data data;
  Producer(Data data) {
    this.data = data;
  }
  public void run() {
    for(int i=0; i<1000000; i++)
      data.write(i);
  }
}
class Consumer extends Thread {
  private Data data;
  Consumer(Data data) {
    this.data = data;
  }
  public void run() {
    while(true)
      System.out.println(""+data.read());
  }
}
このプログラムでは,2 つのスレッド Producer と Consumer を 定義している.各々クラスとして実現し,クラス Thread を継承している. これらのクラスを制御するクラスが Data である. このクラスは,最初に実行されるメソッド main を持っている. メソッド main は,クラス Data,クラス Consumer,クラ ス Producer の各インスタンスを生成し,スレッドとなる両クラスの インスタンスのインスタンスメソッド start を呼出し,スレッドを起 動している.

クラス Producer は,クラス Data中の値 value を インクリメントし,クラス Consumer は,クラス Data中の 値 value の値を表示し続けている.この 2 つの処理は独立に行われる.

このプログラムを実行すると以下のようになる.実行する毎に結果が異なるが, ある実行結果を紹介しよう.

6994
21966
21966
21966
21966
29475
36862
36862
36862
36862
 . . .
いくつか同じ数字が続いた後(クラス Consumerのインスタンスにより), 「自動的に」クラス Producer のインスタンスに切り替えられ,適当な時 間インクリメントされていることがわかる.

2 つのスレッドのタイムチャートを表現すると 図3 のようになる.

図 3: タイムチャート

CPU は,通常 1 つであるが,この 2 つのスレッドを時分割で切り替えている. このようなスレッドを プリエンプティブ スレッド (preemptive thread) という. Java の言語仕様では,このようなスレッドは必ずしも仮定されていないが, 本稿では,プリエンプティブスレッドを仮定する.



平成12年8月9日

Try it: preemption loses updates

Two threads each increment a shared variable 200 times.

0 / 400
T1 · 0 T2 · 0 lost · 0

  

1 つのスレッド

では,スレッドの例をワイヤフレームを使って示そう. ワイヤフレームとして,以下のインタフェースを定義しよう (ファイル名 WireFrame.java).
interface WireFrame {
  public void Draw(Graphics g);
  public void Rotate();
}
この WireFrame を実現し,回転体を描くクラス FreeSoR を 示そう(ファイル名 FreeSoR.java).
import java.awt.*;
import java.io.*;
import java.lang.*;
import java.applet.*;
public class FreeSoR
    implements WireFrame {
  private int xx[] =
    {70,40,30,20,20,30,20,30,70,70};
  private int yy[] =
    {-100,20,30,40,60,70,80,90,94,100};
  private double x[][]=new double[10][10];
  private double y[][]=new double[10][10];
  private double z[][]=new double[10][10];
  private double alpha=Math.PI/15;
  private double beta=Math.PI/15;
  private double gamma=Math.PI/15;
  private double delta=Math.PI/5;
  private Dimension s; private int n;
  FreeSoR(Dimension s,int n) {
    this.s = s; this.n = n; 
    for( int i=0; i<10; i++ ) {
      x[0][i] = xx[i]; y[0][i] = yy[i];
      z[0][i] = 0;
    }
    double x1, z1;
    for( int i=0; i<9; i++ )
      for( int j=0; j<10; j++ ) {
        x1 = x[i][j]; z1 = z[i][j];
        x[i+1][j]=x1*Math.cos(delta)+
                  z1*Math.sin(delta);
        y[i+1][j]=y[i][j];
        z[i+1][j]=-x1*Math.sin(delta)+
                   z1*Math.cos(delta);
      }
  }
  public void Draw(Graphics g) {
    g.setColor(Color.black);
    int h = n / 3; int v = n % 3;
    int x1, y1, x2, y2;
    int wu = s.width/3;
    int hu = s.height/4;
    for( int i=0; i<10; i++)
      for( int j=0; j<10; j++) {
        x1=(int)(x[i][j]/3)+(h+1)*wu;
        y1=(int)(y[i][j]/3)+(v+1)*hu;
        x2=(int)(x[(i+1)%10][j]/3)+
          (h+1)*wu;
        y2=(int)(y[(i+1)%10][j]/3)+
          (v+1)*hu;
        g.drawLine(x1,y1,x2,y2);
      }
    for( int i=0; i<10; i++)
      for( int j=0; j<9; j++) {
        x1=(int)(x[i][j]/3)+(h+1)*wu;
        y1=(int)(y[i][j]/3)+(v+1)*hu;
        x2=(int)(x[i][(j+1)%10]/3)+
          (h+1)*wu;
        y2=(int)(y[i][(j+1)%10]/3)+
          (v+1)*hu;
        g.drawLine(x1,y1,x2,y2);
      }
  }
  public void Rotate() {
    double x1,x2,x3,y1,y2,y3,z1,z2;
    for( int i=0; i<10; i++ )
      for( int j=0; j<10; j++ ) {
        x1=x[i][j];y1=y[i][j];
        z1=z[i][j];x2=x1;
        y2=y1*Math.cos(alpha)-
           z1*Math.sin(alpha);
        z2=y1*Math.sin(alpha)+
           z1*Math.cos(alpha);
        x3=x2*Math.cos(beta)+
           z2*Math.sin(beta);
        y3 = y2;
        x[i][j]=x3*Math.cos(gamma)-
                y3*Math.sin(gamma);
        y[i][j]=x3*Math.sin(gamma)+
                y3*Math.cos(gamma);
        z[i][j]=-x2*Math.sin(beta)+
                 z2*Math.cos(beta);
      }
  }
}
このクラス FreeSoR のインスタンスを生成し,メソッド Rotate を 呼出すことにより回転体を回転するスレッドを生成しょう
(ファイル名 Sample.java).
import java.awt.*;
import java.applet.*;
public class Sample extends Applet 
    implements Runnable {
  private FreeSoR l;
  public void init() {
    l = new FreeSoR(size(),0);
    Thread t = new Thread(this);
    t.start();
  }
  public void paint(Graphics g) {
    l.Draw(g);
  }
  public void run() {
    while(true) { l.Rotate(); repaint(); }
  }
}
このアプレットは,
init により クラス FreeSoR のインスタンスを 一つ作りスレッドとして起動する.関数 size は, Dimension ク ラスのインスタンスを表し,現在の表示領域の大きさを保持している. スレッドとして起動されると,メソッド run が呼出されるので,その中で,クラス FreeSoR のインスタンスの Rotate を連続的に呼出す.その間に repaint を呼出すことによ り,図形を再描画している.

このアプレットを実行するためには,以下の HTML ファイルを必要とする(ファイル名 Sample.html).

<applet code="Sample.class" width=400
    height=400></applet>
このアプレットを実行すると図4 のようになる.
図 4: スレッド 1つ


平成12年8月9日

Try it: the FreeSoR code above, running

Same vertices and angles as the listing above.

課題 1

上のクラス FreeSoR を使って図5 のように 6 つのスレッドを 発生するアプレットを実現せよ(プログラムは a2ps で出力せよ). 余裕のある人は,ダブルバッファの処理もせよ.
図 5: スレッド 6つ


平成12年8月9日

06同期をとる

これまでのスレッドは,各々独立に実行し勝手な処理をしていた. 一般のプログラムでは,このような「勝手な」処理では不都合なことが多く, 各スレッドが「同期をとって」処理することが必要である.

例えば,クラス Data の例を改良して,数字の出力を

0
1
2
3
4
5
. . .
のように順番に数字が表示されるようにしよう
(ファイル名 Semaphore.java).
class Semaphore {
  int count;
  public Semaphore(int value) {
    count = value;
  }
  synchronized public void Wait() {
    if( --count < 0 ) {
      try {
        wait();
      } catch(InterruptedException e) {}
    }
  }
  synchronized public void Signal() {
    if( count++ < 0 ) {
      notify();
    }
  }
  public static void main(String args[])
  {
    Data b = new Data();
    Semaphore prod = new Semaphore(1);
    Semaphore cons = new Semaphore(0);
    Producer p = new Producer(prod,cons,b);
    Consumer c = new Consumer(prod,cons,b);
    p.start(); c.start();
  }
}
class Data {
  private int value;
  public int read() {
    return value;
  }
  public void write(int data) {
    this.value = data;
  }
}
class Consumer extends Thread {
  Data data;
  Semaphore prod, cons;
  Consumer(Semaphore prod,Semaphore cons,
    Data data) {
    this.data = data;
    this.prod = prod;
    this.cons = cons;
  }
  public void run() {
    for(int i=0; i<100000; i++) {
      cons.Wait();
      System.out.println(""+data.read());
      prod.Signal();
    }
  }
}
class Producer extends Thread {
  Data data;
  Semaphore prod, cons;
  Producer(Semaphore prod,Semaphore cons,
    Data data) {
    this.data = data;
    this.prod = prod;
    this.cons = cons;
  }
  public void run() {
    for(int i=0; i<100000; i++) {
      prod.Wait();
      data.write(i);
      cons.Signal();
    }
  }
}
このように,スレッドの一つの処理が終了すると,別のスレッドの 一つの処理が開始し,その処理が終了すると,他のスレッドの処理が開始する, というようにスレッド間でお互いに干渉し合い,処理 を制御することを 同期をとる(synchronize)という.



平成12年8月9日

セマフォ

上で導入したセマフォをタイムチャートを使って表わすと,図6 のようになる.
図 6: タイムチャート(セマフォ)
セマフォと使うと,複数のスレッド間で Wait(), Signal() で囲まれ た部分を処理する毎に,切換えられていることがわかる.これは,図 7 に示 すように,2 つのセマフォにより,処理が制御されているためである.
図 7: セマフォ
各セマフォは,2 つの値を持ち,メソッドを実行できるか,できないかを 表わしている.もし,他のスレッドがそのスレッドを実行している時, スレッドは「待たされる」という処理をする.そして,いずれ,処理可能となっ た時,その処理が再開される.このような 2 つの値しか持たないセマフォを 2 値セマフォ(binary semaphore) という. 2 値セマフォは,図ではよく旗で表現する.各スレッドは,その旗を見て 「処理可能かどうか」を判断し,同期をとっている.さらに,その使い方は簡 単でセマフォで用意されたメソッド Wait(), Signal() で挟めばよい.

例えば,クラス Consumer のインスタンスでは,

cons.Wait();
System.out.println(""+data.read());
prod.Signal();
としている.
Consumer 用のセマフォ cons のメソッド Wait() を呼出し,「データを読み取る処理」( data.read()) を行って よいかを問い合わせている.許可がおりれば data.read() を処理し, 終了した旨を Producer 用のセマフォ prod のメソッド Signal() を呼出し,知らせている. Producer 用のセマフォにも同様に逆の処理を行えばよい.


平成12年8月9日

Try it: P and V

P() takes one unit; if none is left the caller waits.

S = 2
holding
waiting

  

有限バッファ問題

セマフォを使うと簡単に解決することができる代表的な問題である 有限バッファ問題(bounded buffer problem)を紹介しよう. この問題は 2 値ではなく, N 個の値を持つセマフォを考える.通常,セマフォはこちらを指す.

バッファとは,以下のように「ある値」を要素に持つ箱の集まりである. 元々の意味は,緩衝装置の意味で,コンピュータでの装置 (デバイス,device) 間での通信においてデータの流れを調節する場合に用 いられる.

図 8: 有限バッファ問題
有限バッファには,入力ポインタ(input pointer)と, 出力ポインタ(output pointer) があり, 各々データをバッファに入れる位置と,バッファから出す 位置を示す.また,各ポインタにはセマフォを付随させる(2 個のセマフォを 生成). これらのセマフォは 2 値セマフォと違い整数値を保持するものとする.各々,
  1. N - (現在格納されている要素数)
  2. 現在格納されている要素数
を示す.
1. は,「入力ポインタ」に付随しているセマフォで, 「バッファが一杯でないか」をチェックする役目を持つ. 2. は,「出力ポインタ」に付随しているセマフォで, 「バッファが空でないか」をチェックする. ここで,どちらのセマフォも各ポインタが役目を果たせずに,待ち状態になる かをチェックしていることに注意しよう.

では,例題として以下のプログラムを見てみよう.

public class BBuffer {
  int values[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
  int inputp=0; int outputp=0;
  Semaphore isem; Semaphore osem;
  BBuffer() {
    isem = new Semaphore(10);
    osem = new Semaphore(0);
  }
  public void input(int i) {
    isem.Wait();
    values[inputp]=i;
    inputp=(++inputp)%10;
    osem.Signal();
  }
  public int output() {
    osem.Wait();
    int r=values[outputp];
    outputp=(++outputp)%10;
    isem.Signal();
    return r;
  }
  public static void main(String args[])
  {
    BBuffer bbuffer=new BBuffer();
    Producer p = new Producer(bbuffer);
    Consumer c = new Consumer(bbuffer);
    p.start(); c.start();
  }
}
class Producer extends Thread {
  BBuffer bbuffer;
  Producer(BBuffer bbuffer) {
    this.bbuffer=bbuffer;
  }
  public void run() {
    for(int i=0; i<100; i++) {
      System.out.println("input="+i);
      bbuffer.input(i);
    }
  }
}
class Consumer extends Thread {
  BBuffer bbuffer;
  Consumer(BBuffer bbuffer) {
    this.bbuffer=bbuffer;
  }
  public void run() {
    for(int i=0; i<100; i++) {
      System.out.println("output="+
        bbuffer.output());
    }
  }
}
クラス BBuffer のインスタンスにより有限バッファを表現している. バッファに値を入力する場合は input メソッドを使い, バッファから値を出力する場合は output メソッドを使う. 各々バッファへ入力するオブジェクトとして クラス Producer のインスタンスを定義し, バッファから出力するオブジェクトとしてクラス Consumer のインスタンスを定義する. 両オブジェクトはスレッドとして起動される. バッファは有限の大きさ (ここでは 10) しか持っていないので バッファの最後にポインタが来たら,最初にポインタを進める操作も必要であ る.そのため,
inputp=(++inputp)%10;
のように,
ポインタを進めた後にバッファの大きさの剰余をと るようにしている. バッファの要素は -1 で初期化され「要素なし」を意味する. 同期をちゃんと処理すれば,要素が入っているかどうかを気にせずにポインタ の操作のみでうまく処理される.

このプログラムを実行すると以下のようになる.

input=0
input=1
input=2
output=0
output=1
output=2
input=3
input=4
input=5
input=6
input=7
input=8
input=9
output=3
output=4
output=5
output=6
input=10
input=11
input=12
input=13
. . .
最初,クラス Producer のインスタンスにより input メソッドが呼出 され,3 つの値 (1と2,3) が入力される.その後, クラス Consumer のインスタンスに処理が移り,入力された値が取り出 されていることがわかる.再び クラス Consumer のインスタンスに処 理が移りバッファに値が入力されている.

結果として,0 から 99 の値がバッファへ入力され, 更にバッファから取り出されていることが確認できるが, その間も有限の個数しか持たない (10 個) バッファをうまく利用して待ち合 わせ処理が行われている.


平成12年8月9日

Try it: producer and consumer

The producer waits when full, the consumer when empty.

empty = 6 full = 0 produced 0 consumed 0

07課題 2

有限バッファをわかりやすく説明するようなアプレットを実現せよ. セマフォも図の中で表現せよ(図 9,図 10 を参考にせよ).
図 9: 有限バッファアプレット(開始)
図 10: 有限バッファアプレット(処理中)


平成12年8月9日

08課題 3

回転体のスレッドが同期して処理するように上のプログラムを修正せよ. ボタン( sync というラベルをつける)を用意し,ボタンを押す 毎に,同期,非同期を繰り返すようにせよ(図 10)(プログラムは a2psで出 力せよ).
図 11: 同期,非同期を繰り返す


平成12年8月9日

The same material in the other two languages: JavaScript edition / Python edition

These notes were generated from a LaTeX source. The earlier page-per-section version is still available: open the earlier version