このページは、書き直した JavaScript を動かしているのではありません。
1998年に javac が吐いた .class ファイルそのものを読み込み、
JavaScript で書いた JVM がバイトコードを1命令ずつ解釈して実行しています。
Thread も synchronized も wait/notify も、
本物と同じ意味で動いています。
../js/ にあります。
あちらは「同じ絵を出す別のプログラム」、こちらは「当時のバイトコードそのもの」です。
部品は4つだけです。全部このディレクトリにあります。
| classfile.js | .class を読む。先頭の cafebabe から定数プール・フィールド・メソッド・
Code 属性・例外表まで、JVM 仕様 4章のとおりに解く。
1998年のものは major = 45(Java 1.1)。 |
|---|---|
| interp.js | バイトコードを 1命令ずつ実行する。long は BigInt、
double は数値、long/double はスタックと局所変数を 2枠使う、
というところまで仕様どおりに合わせてある。 |
| (同)スレッド | JavaScript には実行の流れが1本しかないので、Java のスレッドはこちらで用意する。
スレッドごとにフレームを持ち、400命令ずつ順ぐりに実行する。
Thread.sleep は起きる時刻を持って眠り、synchronized はモニタが取れなければ待ち、
wait() はモニタを手放して待ち集合に入る。
つまり Semaphore の待ち合わせは、本当に待ち合わせている。 |
| rt.js | アプレットが呼ぶ JDK。逆アセンブルして数え上げたら 38個だったので、それだけ用意した。
java.awt.Graphics は canvas の 2D コンテキスト、
java.awt.Image は画面外の canvas(だからダブルバッファが本当に効く)、
Button と Scrollbar は本物の DOM 部品。 |
背景が灰色なのは飾りではありません。AWT の Panel の既定の背景色(#C0C0C0)で、
当時のスクリーンショット(rot6sync.jpg など)と同じ色です。
正直に分けて書いておきます。公開してあった .class はアプレット本体の7個だけで、
それが呼んでいる FreeSoR や VBB などのバイトコードは、そもそも公開されていませんでした。
| 1998年の .class そのまま |
Sample ・ Sample05 ・ Sample1 ・ Sample15 ・
Sample3 ・ Sample4 の6個。
いずれも major = 45、1998年のバイト列に手を触れていません。 |
|---|---|
| 1998年の .java から compile |
FreeSoR ・ WireFrame ・ TR ・
VBB ・ VBBB ・ Pointer ・
Producer ・ Consumer ・ PProducer ・ CConsumer。
ソースは当時のままで、javac --release 8 に通しただけです。 |
| 失われていたもの | Sample2.class は壊れています。中身が全部 NUL(0x00)で、サーバ上の実体も同じでした。
Sample2.java は無事だったので、そこから compile し直しています。 |
| 壊れていた画像 | 紹介ページ(英語)の親指画像のうち rot1obj.jpg と rot1bobj.jpg も
中身が全部 NUL でした。この JVM で 1998年のバイトコードを動かした画面から作り直してあります。
残り 5枚(rot6obj/rot6bobj/rot6sync/bbuf0/bbuf1)は当時のままです。 |
| 復元したもの | Semaphore.java と TR2.java は、ソースごと失われています。
Sample2/Sample3/Sample4 からの呼ばれ方だけを手がかりに書き直しました
(new Semaphore(int)/Wait()/Signal()/Value()、
new TR2(Dimension,int,Semaphore,Semaphore)/Draw/Value/sync)。
Semaphore.java ・
TR2.java で中身を確かめられます。 |
なお Java アプレット自体が、もうどのブラウザでも動きません(2017年以降)。
java.applet パッケージは JDK 24 で JDK からも取り除かれました。
ここで java.applet.Applet や java.awt.Graphics として動いているのは、
rt.js が用意した中身です。
簡単なクラスを用いて 3D オブジェクトを生成する。断面の10点を Y 軸まわりに 36°ずつ回して回転体を作り、X・Y・Z の各軸まわりに 12°ずつ回し続ける。
Thread を1本立て、80 ミリ秒ごとに Rotate() と repaint() を呼ぶ。
repaint() は上書きせずに既定の update() に落ちるので、毎回背景で消してから描き直す(だからちらつく)。
次は、ダブルバッファを用いて処理をする。createImage() で画面外に絵を作り、
できあがってから 1枚だけ貼る。update() を上書きして「消してから描く」のをやめている。
ここでの画面外バッファは、本当に別の canvas です。
Image.getGraphics() がその canvas の 2D コンテキストを返し、drawImage で貼っています。
これら 3D オブジェクトは、並行に処理される。ここでは、六個のオブジェクトを生成する。
下に出ている「スレッド 7本」は飾りではありません。
TR が 6本(各自 40 ミリ秒ごとに自分を回す)と、80 ミリ秒ごとに repaint() する 1本です。
6本はそれぞれ勝手に回るので、そろいません。
次は、ダブルバッファを用いて処理をする。
絵が落ち着いて見えます。6本が勝手に回っていること自体は、ひとつ上と変わりません。
セマフォを使って、同期・非同期を切替える。絵の中の「Sync」ボタンを押してみてください。
6個のセマフォを輪にして、0番だけ初期値を 1 にしてあります。
各オブジェクトは「自分のセマフォを Wait() → 回す → 隣を Signal()」なので、
同期のときは札が輪を1周ずつ回り、1個ずつ順番にしか回りません。
数字は、そのセマフォの現在値です。
ボタンを押すと action(Event,Object)(AWT 1.0 の古い受け取り方)が呼ばれ、
待ち合わせをやめて 6個が勝手に回り出します。
この待ち合わせは synchronized と wait()/notify() で本当に起きています。
有限バッファ問題を表現する。10枠のバッファに、作り手と使い手が入れたり出したりする。 上下のつまみで、作る速さと使う速さを変えられます。
上の矢印が入れる位置、下の矢印が出す位置。矢印の中の数字はセマフォの値で、
上が「あと何枠空いているか」(初期値 10)、下が「いくつ溜まっているか」(初期値 0)です。
作る方を速くすると上の数字が 0 になり、作り手が Wait() で止まります。
使う方を速くすると下の数字が 0 になり、使い手が止まります。
止まっているのは絵の演出ではなく、そのスレッドが本当にモニタの待ち集合に入っている状態です。
同じものを、数字ではなく●で表したもの。
中身の描き方が drawString から fillOval に変わっただけで、
待ち合わせの仕掛けは同じです。
紹介ページ: index-j.html / 当時のページ: index-j-1998.html / JavaScript で書き直した版: ../js/