WSL の Ubuntu 18.04 で SpiderMonkey をビルドした話
はじめに
ここまで TypeScript(JavaScript) の prototype などを追ってきて、ふと思ったのが、「これって内部的にはどう扱われているのだろう」ということでした。
そこで、 JavaScript を動かすのに使われる JavaScript Engine を触ってみることにしました。
選択の理由
最初 Windows 上で V8 を使ってみようと思ったのですが( Node.js も V8 を使っているということで)、ビルド自体はできたものの、そこからどうしたら・・・?ということで、いったん中断しました。
で、大好きな FireFox でも使われているという SpiderMonkey を試してみることにしました。
これも最初は Windows 上でビルドしようとしたのですが、途中で登場する autoconf2.13 が、どれを使えば良いのか?となったので、 WSL 上の Ubuntu 18.04 を使うことにしました。
WSL のインストール
あえて書くほどのこともないですが。
コントロール パネル > プログラム > プログラムと機能 > Windows の機能の有効化または無効化
から、 Windows Subsystem for Linux にチェックを入れて再起動します。
せっかくなので WSL 2 もインストールします。 (ビルドバージョン 18917 以上でないと、コマンド実行時にエラーになるので注意が必要です)
あとはディストリビューションを Microsoft Store でインストールするだけです。
今回は Ubuntu 18.04 LTS にしました。
雑にユーザー名とパスワードを登録し、 sudo apt update -> sudo apt upgrade をキメておきます。
Windows <--> WSL(Ubuntu) のファイルのやり取り
Windows と WSL でファイルをやり取りするのは、特別なことをしなくても可能です。
この辺り VM にはない便利さがありますね。
Windows -> WSL(Ubuntu)
Windows 上では下記の場所にデフォルトのディレクトリがあります。
- C:/Users/{Windows のユーザー名}/AppData/Local/Packages/CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc/LocalState/rootfs/home/{Ubuntu のユーザー名}
Ubuntu18.04onWindows_ の後ろの部分はインストール時に決まるのでしょうか。
または、アプリのバージョンによっている?
WSL(Ubuntu) -> Windows
WSL 上からは、 /mnt/c が C ドライブになる、と。
ただ、最初 Windows 上に SpiderMonkey のソースを git clone していたため、それを使ってビルドしようとしたら途中で失敗したため、アクセス権的なところで問題があったかと思います。
なので、インストールやビルドするファイルは素直に WSL 上に置くのが良いかと思います。
必要なソフトウェアのインストール
次は SpiderMonkey のビルドに必要なソフトウェアをインストールしていきます。
インストールするものは下記に書かれているわけですが、デフォルトでインストール済みのものと区別がつかない、というところもあって、トライ&エラーで怒られたら足りないものをインストール、という方法で行ったため、不要なもの、重複するものが混じっていたりすると思います。
とにかくインストールしたものは下記の通りです( Rust, Cargo は後述)。
- autoconf2.13 (普通に autoconf でインストールすると 2.6X とかが入るので注意)
- python (Python3 系だとちゃんと動かないらしいです)
- clang
- gcc
- g++
- yasm
- llvm
- make
- clang-8 (多分不要)
- gcc-6 (多分不要)
Rust, Cargo のインストール
SpiderMonkey のビルドには Rust, Cargo が必要です。
が、 apt get install でそれらを入れてしまうと、バージョンが低いとエラーになります。
ver.1.37.0 が必要なわけですが、これをインストールするのは、またリポジトリー追加するのかな~と思っていたら、少し違っていました。
上記を参考に、コマンドを実行していくだけです。
curl https://sh.rustup.rs -sSf | sh ls $HOME/.cargo/bin cat $HOME/.cargo/env source $HOME/.cargo/env
せっかく環境揃ったのだし、 Rust も触ってみても面白そうですね(収拾付かないので今はできませんが)。
SpiderMonkey のビルド
いよいよここからが本番ですよ。
基本的には MDN のドキュメントを参考に進めていくだけです。
ソースコードは git からも入手可能ということで、下記を参考に git clone しました。
ディレクトリをホームに追加して、その下で git clone 。
cd gecko-dev したところからスタートです。
- cd js/src
- autoconf2.13
- mkdir build_OPT.OBJ
- cd build_OPT.OBJ
- ../configure
- make
3.、4.は不要かもしれません(エラーで苦しんでいるときにあれこれ参考にしたサイトの真似をしています)。
ともかく、エラーが発生することなく make が完了すれば OK です。
それにしても、 make の際、ログが固まりでドン!と出てくるのが心臓に悪い(´・ω・`)
実行する
実行ファイルは gecko-dev/js/src/build_OPT.OBJ/js/src にある js です。
ということで、 (build_OPT.OBJ にいる場合) ./js/src/js と実行してやれば >js と表示され、 console.log("hello world!"); と入力してやれば hello world! と表示されます。
さて、動くようになったのは良いとして、コンパイルした結果を見る、 JavaScript のコードを直接書くのではなく、ファイルを読み込む方法は? など試していきたいと思います。
【TypeScript】prototype を追いかける
はじめに
- 【TypeScript】InversifyJSでIoC(DI)体験 1
- 【TypeScript】InversifyJSでIoC(DI)体験 2
- 【TypeScript】Decoratorを覗き見る 1
- 【TypeScript】Decoratorを覗き見る 2
続きです。
が、 Decorator から離れていく気しかしないのでタイトルは変えておきます。
prototype メソッド
mainPage.ts
export class Sample{ private message = "たこやき"; public callMethod(){ console.log("I love " + this.message); this.message = "チーズ"; } public static callStaticMethod(){ console.log("I love " + Sample.prototype.message); Sample.prototype.message = "コーンポタージュ"; } } ~省略~
前回のコードですが、 prototype メソッド( callMethod() )が持つ Object(prototype) をたどってみるため、下記のようにしてみます。
class Sample{ public callMethod(){ ~省略~ } public getThis(){ return this; } } let sample = new Sample(); console.log(sample == sample.getThis()); console.log(sample.callMethod); console.log(sample.callMethod.prototype); console.log(Object.getPrototypeOf(sample.callMethod)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(sample.callMethod))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(sample.callMethod))));
まず上記のコードにおいて、 prototype method である callMethod() や getThis() の中で this と書いた場合、その中身は Sample のインスタンスです( sample )。
そのため、sample.callMethod と callMethod() 内での this.callMethod も同じです。
ということで上記は、sample.callMethod のみで確認しています。
実行結果は以下の通り。
true function callMethod() undefined function () Object { … } null
console.log(sample.callMethod) の中身は function callMethod() となっています。
また、その下の Object.getPrototypeOf(sample.callMethod) を繰り返すところを見てみると、下記のようになっています。
function callMethod() > Function.prototype > Object > null
流れとしては関数(メソッド)のプロトタイプチェーンで説明されている通りかと思います。
{インスタンス}.{prototype メソッド}.prototype = undefined?
一つ気になるのが sample.callMethod.prototype 。
なんで undefined になるの?というか、こないだ実行したときそうなってなかったような。。。?と思っていたら、ターゲットを ES2015 以上に設定している場合、返還後の JavaScript でもクラスが使用されるのですが、その場合 undefined になるようです。
ES2015 未満
"use strict"; var Sample = /** @class */ (function () { function Sample() { } Sample.prototype.callMethod = function () { }; Sample.prototype.getThis = function () { return this; }; Sample.callStaticMethod = function () { }; return Sample; }());
ES2015 以上
"use strict"; class Sample { callMethod() { } getThis() { return this; } static callStaticMethod() { } }
class S{
— 千(Masui Masanori) (@masanori_msl) September 8, 2019
public do(){ }
}
let s = new S();
console.log(https://t.co/8VrucUfTvB.prototype);
実行すると[object Object]になる時とundefinedになる時があって、なんでぞ?と思ってたら、ES2015未満かそれ以上かで変わるのね。
あ~びっくりした。 #TypeScripthttps://t.co/Zi1VPQt52S
あ、そうか。JavaScriptのクラスは関数の糖衣構文とはいえ、内部的にES3のコードと同じようなものになる訳じゃない、ということなんかな?
— 千(Masui Masanori) (@masanori_msl) September 8, 2019
どうにも先にコンパイルしてランタイムが実行、みたいな発想から抜けられん(´・ω・`)
なお、 ES3 に設定した場合の sample.callMethod.prototype の結果は下記の通りです。
{…} constructor: function callMethod() < prototype>: Object {…}
Object.getPrototypeOf(target) と target.prototype
若干今更感ありますが、 Object.getPrototypeOf(target) と target.prototype の違いとは何でしょうか。
Object.getPrototypeOf(target) (古くは proto ) は、 target の内部プロパティである Prototype を取得するものである、と。
内部プロパティというものが今一つ分かっていないのですが、実際の結果を見ると
console.log(sample.callMethod); console.log(Object.getPrototypeOf(sample.callMethod)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(sample.callMethod))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(sample.callMethod))));
この結果が
function callMethod() function () Object { … } null
メソッド > Punction Prototype > Object > null となっていることから、親の Prototype を取得できる、というイメージでしょうか。
では target.prototype は?というところですが、
- Object のプロトタイプ - Web 開発を学ぶ | MDN
- Object.prototype - JavaScript | MDN
- Function.prototype - JavaScript | MDN
この辺りをみると、自身( Constructor 関数)が持つ prototype プロパティであるようです。
…うーん、わかるようなわからないような。。。
この辺りはもう少し調べてみることにします。
prototype メソッドの prototype (図)
先ほどの Object.getPrototypeOf(target) を null が出るまで繰り返した結果を図にしておきます。
static メソッド
次は static メソッドです。
console.log(Sample.callStaticMethod); console.log(Object.getPrototypeOf(Sample.callStaticMethod)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Sample.callStaticMethod))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Sample.callStaticMethod))));
結果は。。。
function callStaticMethod() function () Object { … } null
先ほどとほぼ同じですね。
Sample.callStaticMethod.prototype も同様に undefined になります。
また、 Object.getPrototypeOf(Sample.callStaticMethod)) で得られる Function prototype は prototype method のものと同じです。
(プロパティを追加すると、もう片方でも利用できます。すべきではないと思いますが)
static メソッドの prototype (図)
関数
では関数だとどうでしょうか。
function callFunction(){ } console.log(callFunction); console.log(Object.getPrototypeOf(callFunction)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(callFunction))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(callFunction)))); console.log(callFunction.prototype); console.log(Object.getPrototypeOf(callFunction.prototype)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(callFunction.prototype)));
関数の場合は先ほどまでとは違い、 callFunction.prototype で値を得ることができます。
結果は下記の通り。
function callFunction() function () Object { … } null { … } constructor: function callFunction() < prototype>: { … } Object { … } null
Object.getPrototypeOf(target) で辿った結果だけを見ると、 prototype メソッドも static メソッドも関数も大きな違いはなさそうです。
関数だけ target.prototype が正しく取れるのはなぜ?と思いはしますが、メソッドの方は単にクラス構文が登場して、値の設定の仕方が変わったから、というだけのような気もします。
この辺も突っ込んで調べてみたいところ。
クラス
今度はクラスです。
JavaScript のクラスは関数の糖衣構文である。。。ということは。。。?
class Sample{ public callMethod(){ } public getThis(){ return this; } public static callStaticMethod(){ } } console.log(Sample); console.log(Object.getPrototypeOf(Sample)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Sample))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Sample)))); console.log(Sample.prototype); console.log(Object.getPrototypeOf(Sample.prototype)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Sample.prototype)));
function Sample() function () Object { … } null { … } callMethod: function callMethod() constructor: function Sample() getThis: function getThis() < prototype>: { … } Object { … } null
ほぼ関数と同じですね。
クラスインスタンス
クラスインスタンスはどうでしょうか。
let sample = new Sample(); console.log(sample); console.log(Object.getPrototypeOf(sample)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(sample))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(sample))));
クラスインスタンスは target.prototype を持っていないようです。
結果はこちら。
{} < prototype>: Object { … } {…} callMethod: function callMethod() constructor: function Sample() getThis: function getThis() < prototype>: Object { … } Object { … } null
インスタンス自体は Object である、と。
で、 Object.getPrototypeOf(sample) は Sample.prototype と同じ値です。
マージしてみる
ここまでの図をまとめてみました。
※オレンジ線は参照の意
こうやって見ると、クラスやクラスインスタンスからはメソッドを参照できていますが、メソッドからはほとんどつながりがなく(大元の Object しかない)、雑に引数としてメソッドを渡すと元のクラスインスタンスを見失ってしまう理由がわかるような気がします(気のせいかも)。
proto が非推奨となったり、動的に親となる Prototype が切り替わる、といった部分を考慮していなかったりと単純化してはいるはずですが、やはり複雑ではありますね。
なお、グローバルオブジェクト( Window )はこんな感じでした。
console.log(this); console.log(Object.getPrototypeOf(this)); console.log(Object.getPrototypeOf(Object.getPrototypeOf(this))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this)))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this))))); console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(this))))));
Window WindowPrototype { … } WindowProperties { } EventTargetPrototype { addEventListener: addEventListener(), removeEventListener: removeEventListener(), dispatchEvent: dispatchEvent(), … } Object { … } null
うーむ、ちっとも理解できた気がしない。。。
内部的なところに突っ込むともう少しわかったりするのでしょうか。。。
【TypeScript】Decoratorを覗き見る 2
はじめに
- 【TypeScript】InversifyJSでIoC(DI)体験 1
- 【TypeScript】InversifyJSでIoC(DI)体験 2
- 【TypeScript】Decoratorを覗き見る 1
今回は Decorator Factory から見てみますよ。
Decorator Factory
正直ドキュメントを見ても Class Decorator を返す関数、というだけでどう活用できるものなのか今一つ分かっていなかったのですが、これを使うことで、 Decorator に引数を渡すことができるようになります。
decoratorFactorySample.ts
import { Sample } from "../sample"; export function decoratorFactorySample(name: string, id: number, s: Sample){ return function decoratorSample(constructor: Function){ console.log("generate id:" + id + " name:" + name + " " + constructor); } }
operationSample.ts
import { decoratorFactorySample } from "./decoratorSamples/decoratorFactorySample"; @decoratorFactorySample("john", 1, {call: () => console.log("hello sample"), message: "goodomorning"}) export class OperationSample{ public constructor(){ console.log("constructor death"); } public sayHello(){ console.log("hello world!"); } }
引数の個数や種類に制限は無いようで、上記のように増やしても問題なく実行できました。
逆に引数を 0 にすると。。。
operationSample.ts
import { decoratorFactorySample } from "./decoratorSamples/decoratorFactorySample"; @decoratorFactorySample() export class OperationSample{ public constructor(){ console.log("constructor death"); } public sayHello(){ console.log("hello world!"); } }
おや?これ InversifyJS を利用するコードで見たことあるような。。。
(まだコードを読んでいないので確証はありませんが)
なお当然とも言えますが、この後登場する Method Decorator など Class 以外の Decorator でも使用できます。
Method Decorator
お次は Method Decorator です。
名前の通りメソッドに付与する Decorator ですが、今度は引数が 3 つあります。
methodDecoratorSample.ts
export function methodDecoratorSample(target: any, propertyKey: string, discripter: PropertyDescriptor){ console.log("method decorator start"); console.log(target); console.log(propertyKey); console.log(discripter); console.log("method decorator end"); }
operationSample.ts
import { methodDecoratorSample } from "./decoratorSamples/methodDecoratorSample"; export class OperationSample{ public constructor(){ console.log("constructor death"); } @methodDecoratorSample public sayHello(){ console.log("hello world!"); } }
実行すると下記のようになります。
method decorator start Object { sayHello: sayHello(), … } sayHello Object { value: sayHello(), writable: true, enumerable: true, configurable: true } method decorator end
第一引数はメソッドを定義しているクラス、第二引数は対象のメソッド名、第三引数はメソッドの情報、ということのようです。
出力結果を見ると、 PropertyDescriptor.value は対象のメソッドであるため、 PropertyDescriptor.value.call(target) のようにすれば呼び出すことができます。
が、ドキュメントによると target を ES5 より低く設定している場合は PropertyDescriptor が undefined になるようなので、 ES3 に設定している、またはその恐れがある場合は使わない方が良いかもしれません。
なお Mastering TypeScript 3 では第三引数を optional にしていました(現行バージョンとの違いによるものかもしれませんが)。
なお、 PropertyDescriptor.value を使う以外に関数を実行する方法として、下記があります。
target[propertyKey]();
関数の呼び方
ここまで同じ関数(メソッド)を呼ぶのにいくつかの方法が登場しました。
例えばこんなクラスがあったとして
export class Sample{ private message = "たこやき"; public callMethod(){ console.log("I love " + this.message); this.message = "チーズ"; } public static callStaticMethod(){ console.log("I love " + Sample.prototype.message); Sample.prototype.message = "コーンポタージュ"; } }
これらのメソッドを呼ぶ方法として下記が挙げられます
( 6 、 7 、 8 は 2 、 3 、 4 と同じですが、static メソッドということで追加しました)。
export function doSomething(){ let sample = new Sample(); // 1 sample.callMethod(); // 2 sample.callMethod.call(sample); // 3 sample.callMethod.apply(sample); // 4 sample["callMethod"](); // -- static method -- // 5 Sample.callStaticMethod(); // 6 Sample.callStaticMethod.call(Sample); // 7 Sample.callStaticMethod.apply(Sample); // 8 Sample["callStaticMethod"](); }
1 、5 は C# でもおなじみの方法ですが、他はあまり見慣れない呼び方です。
実行結果は下記の通りです(どれがどの結果かわかるように番号を振っています)。
// 1 I love たこやき // 2 I love チーズ // 3 I love チーズ // 4 I love チーズ // 5 I love undefined // 6 I love コーンポタージュ // 7 I love コーンポタージュ // 8 I love コーンポタージュ
1 ~ 4 までのインスタンスを作って呼んでいるメソッド( prototype メソッド)は、呼び方はともかく想定通りの動きではあります。
5 は2回繰り返すと、他の static メソッド同様「I love コーンポタージュ」となります。
ということは、プロパティ message の定義時に渡している「たこやき」を受け取ることはできず、 かつ static メソッド間では状態が保持されていることがわかります。
なお最後に 1 を追加するともう一度「I love チーズ」と出力されるため、 prototype メソッド側(というか生成したインスタンス)も static メソッドの状態を見ていないことがわかります。
早速それぞれのメソッドを見ていきたいのですが( prototype も気になる)、その前に(見た目上)なじみのある書き方である 1 、5 について。
実は、 JavaScript(TypeScript) におけるクラス、というのは、関数の糖衣構文であるとのこと。
そのため先ほどのクラスは、 ES3 で変換すると下記のようになります( TypeScript Playground で確認) 。
"use strict" var Sample = /** @class */ (function () { function Sample() { this.message = "たこやき"; } Sample.prototype.callMethod = function () { console.log("I love " + this.message); this.message = "チーズ"; }; Sample.callStaticMethod = function () { console.log("I love " + Sample.prototype.message); Sample.prototype.message = "コーンポタージュ"; }; return Sample; }());
インスタンスを作って呼んでいる callMethod はその名の通り prototype を通して、 static メソッドである callStaticMethod はそのまま Sample につながっています。
いつも通り迷走感がすごいですが、今回は prototype を見てみることにしましょう。
Prototype
- クラス · JavaScript Primer #jsprimer
- プロトタイプオブジェクト · JavaScript Primer #jsprimer
- static - JavaScript - MDN
- Eloquent JavaScript
- JavaScript: The Definitive Guide
上記を見ると、まず JavaScript では、関数(メソッド)・プロパティなどの種類にかかわらず、そのほとんどが prototype という Object を継承していると。
先ほどの実験から prototype メソッドと static メソッドで見ているものが違う、ということはわかりました。
ではそれぞれどう違うのか、とりあえずコンソールに出力して比べてみます。
まずは prototype メソッドから。
mainPage.ts
export function doSomething(){ let sample = new Sample(); console.log(sample.callMethod); console.log(sample.callMethod.prototype); }
結果はこちら。
// console.log(sample.callMethod); callMethod() length: 0 name: "" prototype: {…} constructor: function callMethod() < prototype>: Object { … } < prototype>: () apply: function apply() arguments: bind: function bind() call: function call() caller: constructor: function Function() length: 0 name: "" toSource: function toSource() toString: function toString() Symbol(Symbol.hasInstance): function Symbol.hasInstance() < get arguments()>: function arguments() < set arguments()>: function arguments() < get caller()>: function caller() < set caller()>: function caller() < prototype>: Object { … } // console.log(sample.callMethod.prototype); {…} constructor: function callMethod() < prototype>: {…} __defineGetter__: function __defineGetter__() __defineSetter__: function __defineSetter__() __lookupGetter__: function __lookupGetter__() __lookupSetter__: function __lookupSetter__() __proto__: constructor: function Object() hasOwnProperty: function hasOwnProperty() isPrototypeOf: function isPrototypeOf() propertyIsEnumerable: function propertyIsEnumerable() toLocaleString: function toLocaleString() toSource: function toSource() toString: function toString() valueOf: function valueOf() < get __proto__()>: function __proto__() < set __proto__()>: function __proto__()
では static メソッド。
mainPage.ts
export function doSomething(){ console.log(Sample.callStaticMethod); console.log(Sample.callStaticMethod.prototype); }
結果です。
// console.log(Sample.callStaticMethod); callStaticMethod() length: 0 name: "" prototype: {…} constructor: function callStaticMethod() < prototype>: Object { … } < prototype>: () apply: function apply() arguments: bind: function bind() call: function call() caller: constructor: function Function() length: 0 name: "" toSource: function toSource() toString: function toString() Symbol(Symbol.hasInstance): function Symbol.hasInstance() < get arguments()>: function arguments() < set arguments()>: function arguments() < get caller()>: function caller() < set caller()>: function caller() < prototype>: Object { … } // console.log(Sample.callStaticMethod.prototype); {…} constructor: function callStaticMethod() < prototype>: {…} __defineGetter__: function __defineGetter__() __defineSetter__: function __defineSetter__() __lookupGetter__: function __lookupGetter__() __lookupSetter__: function __lookupSetter__() __proto__: constructor: function Object() hasOwnProperty: function hasOwnProperty() isPrototypeOf: function isPrototypeOf() propertyIsEnumerable: function propertyIsEnumerable() toLocaleString: function toLocaleString() toSource: function toSource() toString: function toString() valueOf: function valueOf() < get __proto__()>: function __proto__() < set __proto__()>: function __proto__()
うーん。。。 メソッド名の違いを除くとあまり違っているようには見えませんね。。。
見てるところが違っているのかしら。。。
一旦切ります。
【TypeScript】Decoratorを覗き見る 1
はじめに
ここまで InversifyJS のコードを追いかけてみたわけですが、どうもどのようにしてインスタンスを生成してきているのかがわからない。。
もちろん詳しいところは get の部分を追う必要はあるのですが、一旦その前に関連していそうな Decorator を調べてみることにしました。
Decorator について
雑に調べたところ、下記のような特徴があるようです。
- 定義されたクラスに要素(プロパティや関数など)を動的に追加できる。
- その中身は関数である。
- @関数名 のように Java におけるアノテーションのようにクラスや関数、プロパティに付与して使用する。
- Decorator の関数の引数は付与対象(クラス、関数、プロパティ)によって異なる。
- 実行されるのは Decorator が付与されたクラスが定義された時であり、インスタンス化された時ではない。
- まだ Experimental な機能であり、デフォルトでは有効になっていない。
準備
前述の通り、デフォルトでは有効になっていないため、 tsconfig.json を変更します。
Decorator には直接関係ないところも混ざっていますが、下記のように設定しました。
tsconfig.json
{ "compilerOptions": { "target": "es5", "module": "commonjs", "lib": ["dom", "es6"], ~省略~ "outDir": "dist/js", ~省略~ "strict": true, ~省略~ /* Experimental Options */ "experimentalDecorators": true, "emitDecoratorMetadata": true, } }
で、 HTML からの呼び出し用の関数と Decorator 付与用のクラスを用意します。
import/export はいつも通り webpack を使用します。
mainPage.ts
import { OperationSample } from "./operationSample"; export function doSomething(){ console.log("start"); let o = new OperationSample(); o.sayHello(); console.log("end"); }
operationSample.ts
export class OperationSample{ public constructor(){ console.log("constructor death"); } public sayHello(){ console.log("hello world!"); } }
MainPage.html
< !DOCTYPE html> < html lang="ja"> < head> < meta charset="utf-8"> < title>Decorator Sample< /title> < /head> < body> < button onclick="Page.doSomething()">hello< /button> < script src="../js/main.bundle.js">< /script> < /body> < /html>
これでボタンを押すと下記が出力されます。
start constructor death hello world! end
Class decorator
まず Class に Decorator を付与してみます。
classDecoratorSample.ts
export function classDecoratorSample(constructor: Function){ console.log("classDecoratorSample"); // OperationSample の constructor が実行される. // 戻り値は undefined. let c = constructor(); }
operationSample.ts
import { classDecoratorSample } from './classDecoratorSample'; @classDecoratorSample export class OperationSample{ public constructor(){ console.log("constructor death"); } public sayHello(){ console.log("hello world!"); } }
Class decorator として渡される関数の引数は、Decorator が付与されたクラス(今回は OperationSample )の constructor です。
そのため、上記のコードを実行するとページを読み込んだ時点で OperationSample の constructor が実行され、「constructor death」が出力されます。
なおコメントとして記載しましたが、 C# などとは違い constructor の戻り値はそのクラスのインスタンスではありません。
(undefined となる)
じゃあ初期化できるだけ?かというとそうではありません。
( constructor に限りませんが) TypeScript(JavaScript) の関数は prototype という object を継承しています。
この prototype 、デフォルトでは any 型ですが、アサーションを使って元のクラスの型( OperationSample )に変換することができます。
classDecoratorSample.ts
export function classDecoratorSample(constructor: Function){ ~省略~ // OperationSample として扱うことができる. let c = constructor.prototype as OperationSample; // hello world! と出力される. c.sayHello(); }
ちなみにこのアサーション、「as OperationSample」のクラス名が「as "OperationSample"」と文字列であっても、正しく OperationSample 型に変換してくれたりします。
classDecoratorSample.ts
export function classDecoratorSample(constructor: Function){ ~省略~ // 先ほど同様 OperationSample に変換される. let c = constructor.prototype as "OperationSample"; // hello world! と出力される. c.sayHello(); }
ただし TypeScript 上の型は "OperationSample" であるため、 c.sayHello() の部分はコンパイルエラーになりますが。。。
前回までのコードを見返したり、 get を見ないとわかりませんが、 InversifyJS で依存クラスのインスタンスを取ってくるあたりに関係していそうな気がします。
元クラスで定義されていない関数やプロパティを追加する
またこの prototype に OperationSample で定義されていないプロパティや関数を渡すことで、 OperationSample にプロパティや関数を追加できます。
classDecoratorSample.ts
export function classDecoratorSample(constructor: Function){ console.log("classDecoratorSample"); constructor.prototype.greeting = () => console.log("こんにちは"); }
あとはこのように呼んでやれば OK です。
mainPage.ts
export function doSomething(){ ~省略~ o.greeting(); ~省略~ }
が、当然ながら定義が見つからないとコンパイルエラーになります。
定義ファイルを用意することで、これを防ぐことができます。
下記を参考に src/types というディレクトリ以下に operationSample.d.ts というファイルを作成したところ、コンパイルエラーが解消されました。
operationSample.d.ts
import {OperationSample} from "../ts/operationSample" declare module "../ts/operationSample"{ export interface OperationSample{ greeting(): any; } }
定義の中身
さて気になるのはその中身。
「declare module ~」の部分は、 Ambient Modules と呼ばれる機能で、既存の JavaScript ライブラリなどを使う際に定義を追加・設定するために使われるものであるようです。
まぁコードの見た目的にも、 operationSample.ts というモジュールに対して OperationSample という interface を設定している、という内容に読めます。
んで、その OperationSample という interface についてです。
この interface の名前はクラス名と同じなわけですが、 C# 脳だとどうしても「なぜ同じ名前?エラーにならないの?」と思ってしまいます。
両者の名前が同じである必要があるのは、 TypeScript の interface が open-ended であるため(だと思います)です。
open-ended というのは、このような コードがあったとして、
interface ISample{ greeting(): void; } interface ISample{ doNothing(): void; }
最終的に下記の interface と同じ結果になる、ということです。
interface ISample{ greeting(): void; doNothing(): void; }
で、最初これを interface 間だけの話だと思っていたのですが、これにクラスを加えてみると。。。
class ISample{ } interface ISample{ greeting(): void; } interface ISample{ doNothing(): void; }
ISample というクラスが 2 つの interface を継承した状態となります。
function doSomething(){ let s = new ISample(); s.greeting(); s.doNothing(); }
まぁ実装していないので動かないのですが。。。
ということで、用意した定義ファイルによってクラス OperationSample は interface の OperationSample を継承した状態になるため、関数 greeting() が呼び出し元からも見られるようになった、というわけですね。
なるほどなるほど。面白いですねぇ :)
ということで長くなってきたので一旦切ります。
【TypeScript】InversifyJSでIoC(DI)体験 2
はじめに
*【TypeScript】InversifyJSでIoC(DI)体験 1
続きです。
今回は Container クラスを中心に、もう少し InversifyJS の中身を見ていきたいと思います。
ContainerOptions
前回は使用しませんでしたが、 Container クラスの Constructor では引数としてオプションを指定できます。
内容は Wiki の通りなのですが、せっかくなので試してみますよ。
interfaces/interfaces.ts
~省略~ export interface ContainerOptions { autoBindInjectable?: boolean; defaultScope?: BindingScope; skipBaseClassChecks?: boolean; } ~省略~
autoBindInjectable
autoBindInjectable を true にすると、下記のようにインスタンスを取得できるようになります。
sampleContainer.ts
const sampleContainer = new Container({ autoBindInjectable: true }); sampleContainer.bind< Sample>(TYPES.Sample) .to(DecoratorSample) .inSingletonScope();
mainPage.ts
export function doSomething(){ // sampleContainer.get< Sample>(TYPES.Sample) のように指定する必要がない. let s = sampleContainer.get(DecoratorSample); }
一瞬良いかも、と思ってみたのですが、これだと呼び出す側も実装クラスが誰なのかを知っている必要があるため、どうなんだろう……と思ってしまいました。
もちろん私が気づいていないだけで良い使い方があるかもしれませんが。
defaultScope
defaultScope を指定すると、前回登場した inSingletonScope() などのスコープ設定をデフォルトで設定できます。
sampleContainer.ts
// Default Scope無し. const sampleContainer1 = new Container(); sampleContainer.bind< Sample>(TYPES.Sample) .to(DecoratorSample) .inSingletonScope(); // Default Scopeあり. const sampleContainer2 = new Container({ defaultScope:"Singleton" }); sampleContainer2.bind< Sample>(TYPES.Sample) .to(DecoratorSample);
sampleContainer1 、 sampleContainer2 ともにシングルトンで DecoratorSample のインスタンスが設定されます。
skipBaseClassChecks
一番よく分かっていない機能ですが、拡張クラスを Inject したい場合に便利らしいです。
interface について
ちょっとわき道にそれますが。
Container クラスの Constructor の引数は ContainerOption です。
が、実際には下記のように引数を渡しています。
const sampleContainer = new Container({ defaultScope:"Singleton" });
あれ? ContainerOption (の具象クラス) ではない?
また、先ほど見逃していましたが、 TypeScript の interface は C# とは異なり、変数(プロパティ)を持たせることもできるようです。
interfaces/interfaces.ts
~省略~ export interface ContainerOptions { autoBindInjectable?: boolean; defaultScope?: BindingScope; skipBaseClassChecks?: boolean; } ~省略~
また、具象クラスを定義してインスタンスを作らなくても、 interface が持つプロパティや関数を実装していれば同じものとして扱うことができます。
(この辺りは Go などと(今も仕様が変わっていなければ)共通していますね)
例えばこういうクラスと interface があったとして。
export interface Sample{ message: string; call(): any; } export class IntefaceSample{ public constructor(sample: Sample){ console.log(sample.message); } }
IntefaceSample のコンストラクタに下記を渡すことができます。
let sample = { message: "hello", call: () => {} }; let s = new IntefaceSample(sample);
先ほどの Container のコンストラクタではこの仕組みを利用していたわけですね。
あまり調子に乗って使いすぎるとわけわからん状態になりそうではありますが、こういう柔軟さは良いですね。
Container を追う
さて、 InversifyJS に戻りますよ。
まずは DI Container である(はずの) Container クラスのコンストラクタからたどっていきます。
コンストラクタの最初の処理は先ほどの ContainerOption の設定(引数で渡されていれば)を行っています。
後々 Option の指定によってどのような処理が行われるか、ということにも触れることにはなると思いますが、ここではスキップします。
で、残りの処理はこちら。
container/container.ts
~省略~ public constructor(containerOptions?: interfaces.ContainerOptions) { ~省略~ this.id = id(); this._bindingDictionary = new Lookup< interfaces.Binding< any>>(); this._snapshots = []; this._middleware = null; this.parent = null; this._metadataReader = new MetadataReader(); } ~省略~
id
処理は utils/id.ts にあります。
やっていることは 0 からカウントアップした値を返す、というだけのシンプルなものです。
C# で Equals をオーバーライドする時に出てきた HashCode のようなものでしょうか。
_bindingDictionary
Lookup 、 Binding といういかにも重要そうなクラス、 interface が登場しました。
Lookup は container/lookup.ts 、 Binding は interfaces/interfaces.ts で定義されています。
_metadataReader
planning/metadata_reader.ts で定義されているクラスで、 metadata という名前や Reflect.getMetadata() を呼んでいたりと、 inject するクラスの取得などに関連していると思われます。
( @injectable() を付与するクラスで import が必要だった metadata_reader がコレですね)
空にしているだけであったためスキップした snapshots 、 middleware 、 parent も気になるところですが、さくさく次に進みましょう。
Container.bind
Inject する interface と具象クラスからインスタンスを取得する bind を見てみます。
container/container.ts
~省略~ // Registers a type binding public bind< T>(serviceIdentifier: interfaces.ServiceIdentifier< T>): interfaces.BindingToSyntax< T> { const scope = this.options.defaultScope || BindingScopeEnum.Transient; const binding = new Binding< T>(serviceIdentifier, scope); this._bindingDictionary.add(serviceIdentifier, binding); return new BindingToSyntax< T>(binding); } ~省略~
まず 「const scope = this.options.defaultScope || BindingScopeEnum.Transient;」であれ?と思いましたが、これは this.options.defaultScope が undefined であった場合は BindingScopeEnum.Transient が渡されます。
ということで、何も指定しなかった場合のデフォルトのスコープは Transient になるのですね。
と思っていたら(当然ながら)ドキュメントで触れられていました。
serviceIdentifier
引数の serviceIdentifier ですが、前回使用した string 、 symbol の他 Newable つまりクラス、抽象クラスも扱うことができるようです。
interfaces/interfaces.ts
~省略~ export type ServiceIdentifier< T> = (string | symbol | Newable< T> | Abstract< T>); ~省略~
binding
bindings/binding.ts で定義されているクラスですが、ここでは先ほどの serviceIdentifier とスコープ情報を保持しています。
ここで作られた Binding< Sample>("Sample", BindingScopeEnum.Singleton) が、 container/lookup.ts の Map に格納されます。
container/lookup.ts
~省略~ private _map: Map< interfaces.ServiceIdentifier< any>, T[]>; ~省略~
この値は途中で出てきた _bindingDictionary に格納され、 Container クラスからアクセスされると。
で、この関数の中で最後に返されるのは syntax/binding_to_syntax.ts の BindingToSyntax< T> です。
BindingToSyntax< T>
BindingToSyntax クラスでは Binding< Sample>("Sample", BindingScopeEnum.Singleton) を持っています。
(コンストラクタで渡される)
んで、はじめに戻って今回作成した sampleContainer を見てみると、 to() を呼んでいます。
sampleContainer.ts
sampleContainer.bind< Sample>(TYPES.Sample) .to(DecoratorSample);
syntax/binding_to_syntax.ts
~省略~ public to(constructor: new (...args: any[]) => T): interfaces.BindingInWhenOnSyntax< T> { this._binding.type = BindingTypeEnum.Instance; this._binding.implementationType = constructor; return new BindingInWhenOnSyntax< T>(this._binding); } ~省略~
この引数で DecoratorSample (interface Sample の具象クラス)の情報が _binding に追加されます。
BindingInWhenOnSyntax
syntax/binding_in_when_on_syntax.ts で定義されているクラスです。
in when on 。。。 いえ、良いんですよ。
syntax/binding_in_when_on_syntax.ts
~省略~ public constructor(binding: interfaces.Binding< T>) { this._binding = binding; this._bindingWhenSyntax = new BindingWhenSyntax< T>(this._binding); this._bindingOnSyntax = new BindingOnSyntax< T>(this._binding); this._bindingInSyntax = new BindingInSyntax< T>(binding); } ~省略~
要は in when on でそれぞれインスタンスを作っていると。
Container インスタンスを保持している sampleContainer.ts で以前のように inSingletonScope() を実行した場合、 this._bindingInSyntax が使用されるようですね。
ここまでで、 interface 、具象クラス、スコープ情報が保持されているところが何となく見られてような気がします。
スコープの種類にかかわらず、実際にインスタンスが作られるのは最初に Container にインスタンスを取りに行ったタイミング。
ということで、 get を追うといよいよインスタンスが生成されたり保持されたりするところが見られる、ということ。。。だと思います。多分。
【TypeScript】InversifyJSでIoC(DI)体験 1
はじめに
諸般の事情から TypeScript に漬け込まれている今日この頃。
ふと TypeScript 単体で DI ってできないのかな~と思ってググった時に出てきた、 InversifyJS を試してみることにしました。
TypeScript 単体で~というのは、(今回の InversifyJS でも使われている) Decorator を使うとできそうなので、こちらも引き続き追いかけてはいきたいと思います。
なおタイトルでわざわざ IoC(Inversion of control) を出したのは、 InversifyJS の名前がそこから来てるんだろうな~と思ったためで大した理由はありません。
準備
何はともあれインストールです。
GitHub の手順に従って進めますよ。
npm install --save-dev inversify reflect-metadata
tsconfig.json
{ "compilerOptions": { /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "lib": ["dom", "es6"], /* Specify library files to be included in the compilation. */ ~省略~ "outDir": "wwwroot/js", /* Redirect output structure to the directory. */ ~省略~ "strict": true, /* Enable all strict type-checking options. */ ~省略~ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ ~省略~ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ ~省略~ "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ } }
コメントアウトされた部分は省略しています。また変更したところは太字にしています。
あと、ベースにしているプロジェクトは以前使ったものをそのまま再利用している関係もあり、 webpack を使っています。
exports が使えない?
以前は HTML から呼び出す関数を下記のように書いていました。
exports.doSomething = function(){ console.log("hellllloooo"); }
が、 tsconfig.json で「"types": ["reflect-metadata"],」を有効にしようとするとエラーになってしまいました。
どういう関係があるのはよくわかっていないのですが。。。
で、下記のように変更しました。
export function doSomething(){ console.log("hellllloooo"); }
呼び出す側は変わらず Page.doSomething() で OK です。
……いや、これできるならそもそもこれでよかったんやけど感。
ま、まぁいいや。
とりあえず DI ってみる
準備もできたところで早速使ってみましょう。
sample.ts
export interface Sample{ call(): any; }
DI で挿入する interface です。
特に変わったところはないです。
decoratorSample.ts
import { Sample } from "./sample"; import 'reflect-metadata'; import {injectable} from 'inversify'; @injectable() export class DecoratorSample implements Sample{ public call(){ console.log("hello2"); } }
Sample の実装クラスです。
@injectable() なる気になるワードが。
types.ts
const TYPES = { Sample: Symbol.for("Sample") } export { TYPES }
次に登場する Container で使用します。
sampleContainer.ts
import { Container } from "inversify"; import { TYPES } from "./types"; import { Sample } from "./sample"; import { DecoratorSample } from "./decoratorSample"; const sampleContainer = new Container(); sampleContainer.bind< Sample>(TYPES.Sample).to(DecoratorSample); export{ sampleContainer }
ここで Inject される interface と実装クラスの紐づけが行われています。
では Inject されたものを利用してみます。
mainPage.ts
import { Sample } from "./sample"; import { sampleContainer } from "./sampleContainer"; import { TYPES } from "./types"; export function doSomething(){ let s = sampleContainer.get< Sample>(TYPES.Sample); s.call(); }
ASP.NET Core のコンストラクタインジェクションなどより長くなってしまっているのは気になりますが、これでインスタンスを取得できます。
ただしシングルトンではない
このままだとシングルトンではないため、上記とは別に sampleContainer.get でインスタンスを取得すると別インスタンスが渡されてしまいます。
この指定は Container で行います。
sampleContainer.ts
~省略~ const sampleContainer = new Container(); sampleContainer.bind< Sample>(TYPES.Sample) .to(DecoratorSample) .inSingletonScope(); ~省略~
reflect-metadata 大事
注意すべき点は Inject 対象のクラスで reflect-metadata を import することです。
これを忘れると下記のエラーが発生します。
TypeError: Reflect.hasOwnMetadata is not a function
コンパイルエラーは発生せず、見落としがちなので注意が必要です。
InversifyJS とたわむれる 1
というわけで無事 DI できました。
もう少し中身を見てみることにします。
インスタンス生成のタイミング
Inject 対象のクラスインスタンスですが、いつ生成されるのでしょうか。
decoratorSample.ts
~省略~ @injectable() export class DecoratorSample implements Sample{ public constructor(){ console.log("Constructor death"); } public call(){ console.log("hello2"); } }
mainPage.ts
~省略~ export function doSomething(){ console.log("start"); let s = sampleContainer.get< Sample>(TYPES.Sample); s.call(); }
これを実行すると、出力結果は下記のようになります。
start Constructor death hello2
では Inject 対象のクラスが二つ以上ある場合はどうでしょうか。
doSomething.ts
export interface DoSomething{ saySomething():void; }
diDoSomething.ts
import 'reflect-metadata'; import {injectable} from 'inversify'; import { DoSomething } from './doSomething' @injectable() export class DiDoSomething implements DoSomething{ public constructor(){ console.log("DoSomething constructor death"); } public saySomething():void{ console.log("ho"); } }
types.ts
const TYPES = { Sample: Symbol.for("Sample"), DoSomething: Symbol.for("DoSomething") } export { TYPES }
sampleContainer.ts
import { Container } from "inversify"; import { TYPES } from "./types"; import { Sample } from "./sample"; import { DecoratorSample } from "./decoratorSample"; import { DoSomething } from "./decoratorSamples/doSomething"; import { DiDoSomething } from "./decoratorSamples/DiDoSomething"; const sampleContainer = new Container(); sampleContainer.bind< Sample>(TYPES.Sample) .to(DecoratorSample) .inSingletonScope(); sampleContainer.bind< DoSomething>(TYPES.DoSomething) .to(DiDoSomething) .inSingletonScope(); export{ sampleContainer}
mainPage.ts
~省略~ export function doSomething(){ console.log("start"); let s = sampleContainer.get< Sample>(TYPES.Sample); s.call(); }
これを実行すると、出力結果は下記のようになります。
start Constructor death hello2
ということで、下記のことがわかりました。
- ( inSingletonScope の場合)最初に sampleContainer.get が実行されたタイミングで実体化される。
- sampleContainer.get が実行されていないクラスは同じ Container にバインドされていても実体化されない。
TYPES
Container への bind や get で使用されている TYPES 。
types.ts
const TYPES = { Sample: Symbol.for("Sample"), DoSomething: Symbol.for("DoSomething") } export { TYPES }
今回の用途である sampleContainer.get< Sample> の引数の型は string であり、その内容は Inject される interface 名となります。
(実は実装クラス名でも問題なく動作はしますが、あえてそうするメリットはなさそうです)
sampleContainer.get< Sample>("Sample") のように直接 string で指定することはできますが、引数の名前が実際のクラス名( interface 名)と違っていると実行時にエラーが発生するため、一か所にまとめるという発想は理解ができます。
が、それを const Sample: string = "Sample"; のようにせずに Symbol を使っているのはなぜでしょうか。
Symbol
- Symbol - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
- Symbol - JavaScript | MDN
- JavaScript のデータ型とデータ構造 - JavaScript | MDN
- Symbols in ECMAScript 6
- シンボル型
そもそも Symbol とは?という話になるわけですが、上記を見たところ以下のような特徴があるようです。
- ECMAScript 6 から登場したプリミティブ型
- ユニークかつ不変である
- string と同じくプロパティのキーとして使用できる
特に 2.の特徴から ID として使われることから、今回も string ではなく Symbol が使われているようです。
なお、 InversifyJS の README によると、 Symbol 、 string での指定の他、 class でも指定できるようです。
ただし Symbol 推奨である、ということでした。
なお、 Symbol について注意が必要なのは、下記かなと思っています。
console.log(Symbol.for("Sample") === Symbol.for("Sample")); console.log(Symbol("Sample") === Symbol("Sample"));
結果は下記になります。
true false
Symbol("Sample") はユニークな値を作るのに対し、 Symbol.for("Sample") はグローバルシンボルテーブルと呼ばれる場所に値を作成し、ページをまたいだりしても Symbol.for の引数が同じであれば同じ値を返します。
そのため上記はこのようにしても true となります。
console.log(Symbol.for("Sample") == Symbol.for("Sample"));
InversifyJS については引き続き中身を追っていきたいと思います。
Symbol についても使い方が今一つよくわかってはいないため、ゆるゆると自分でも使用していきたいと思います。
【Java】 volatie に触れてみたい 1
はじめに
前回ちょこっと出てきた volatile 。
CompletableFuture.java
~省略~ volatile Object result; ~省略~
名前すら触れませんでしたが Twitter で流れてきたブログを見て気になっていたこともあり、調べてみることにしました。
volatile とは
とりあえずググってみます。
- Javaの理論と実践: volatile を扱う - IBM Developer 日本語版
- Chapter 8. Classes - The Java® Language Specification
- 3. スレッドの排他制御 (3) - TECHSCORE(テックスコア)
- Javaスレッドメモ - Hishidama's Java thread Memo
- volatileとか使うなと怒られた話 - 谷本 心 in せろ部屋
まとめると下記のような特徴があるらしい、と。
- 変数に付加する(例: 「volatile int count = 0;」)
- マルチスレッド処理の文脈で使用される
- synchronized より軽量に扱うことができる
- synchronized と違ってアトミック性を持たない
- 可視性を持っており、複数のスレッドから同じ変数を変更した場合も、
値を取得するときに(スレッドごとに持っている古い値ではなく)新しい値を取得できる - 取り扱いが難しく、正しく使用できる状況は限られている
とりあえずアトミック性、可視性について見てみます。
アトミック性について
先に挙げた通り、 volatile を変数に付けたとしても、アトミック性は保証されません。
ということは、複数のスレッドから同時に値を変更した場合に、正しい結果にならない場合がある、ということです。
これを 1.通常、2. volatile を付けた場合、3. synchronized を付けた場合、4.変数を AtomicInteger にした場合で試してみます。
まず変数の保持、変更を行うためのクラスです。
3.以外はこのクラスを複数のスレッドから呼び出して最後の値を比較します。
SampleBehaviour.java
package SearchContainedFiles; public interface SampleBehaviour { void add(); void subtract(); int getCount(); }
MultithreadSample.java
package SearchContainedFiles; class MultithreadSample implements SampleBehaviour{ private int count; public void add(){ count++; } public void subtract(){ count--; } public int getCount(){ return count; } }
int の値を +1 / -1 するだけです。
1.通常
App.java
package SearchContainedFiles; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class App { public static void main(String[] args) { MultithreadSample s = new MultithreadSample(); CompletableFuturecallSample1 = CompletableFuture.supplyAsync(() -> add(s)); CompletableFuture callSample2 = CompletableFuture.supplyAsync(() -> subtract(s)); var all = CompletableFuture.allOf(callSample2, callSample1); try { all.join(); all.get(); System.out.println("call: " + s.getCount()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } private static int add(SampleBehaviour s){ System.out.println("call1 " + Thread.currentThread().getId()); for(int i = 0; i < 10000; i++){ try{ s.add(); } catch(IllegalArgumentException e){ System.out.println(e.getLocalizedMessage()); } } return 0; } private static int subtract(SampleBehaviour s){ System.out.println("call2 " + Thread.currentThread().getId()); for(int i = 0; i < 10000; i++){ try{ s.subtract(); } catch(IllegalArgumentException e){ System.out.println(e.getLocalizedMessage()); } } return 0; } }
+1 / -1 するメソッドを 10000 回ずつ呼び、最後に値を受け取ります。
期待値は 0 であり、「call: 0」になるはずです。
結果
出力結果は下記の通りです。
call1 13 call2 14 call: -35
Oh......
ややこしいのは、実行されるタイミングによっては正しい結果が出てくることです。
その場合、 s.add() や s.subtract() の前後で Thread.sleep してやると再現しやすい気がします。
アトミック
+1 / -1 を同じ回数実行しても 0 に戻らないのは、 int がアトミックではないためです。
例えば add() を実行するとき、実際には下記の処理が行われることになります。
- count の値を取得する
- count に 1 を追加する
- count の値を上書きする
1.が実行され、 3.が実行されるより前に subtract() が実行されてしまうと、 3.の時点での count の値が 1.と異なってしまうため、ズレが生じてしまう、ということのようです。
これを防ぐ方法は?というのが、後述する AtomicInteger や synchronized です。
2. volatile を付けた場合
とその前に、今回の主役である volatile を試してみます。
MultithreadSample.java
package SearchContainedFiles; class MultithreadSample implements SampleBehaviour{ private volatile int count; public void add(){ count++; } public void subtract(){ count--; } public int getCount(){ return count; } }
変数に volatile をつけただけですが、果たしてどうなるか......
call1 13 call2 14 call: -363
デスヨネー。
volatile はアトミック性は保証しないため、今回の実験結果としては特に変化がありません。
3. synchronized を付けた場合
~省略~ class MultithreadSample implements SampleBehaviour{ private int count; public synchronized void add(){ count++; } public synchronized void subtract(){ count--; } ~省略~
結果は
call1 13 call2 14 call: 0
正しく 0 になりました。
興味深いのは、下記のようにコンソールに出力されるようにしてみると......
~省略~ class MultithreadSample implements SampleBehaviour{ private int count; public synchronized void add(){ System.out.println("add " + count); count++; } public synchronized void subtract(){ System.out.println("subtract " + count); count--; } ~省略~
このような出力結果になります。
subtract 0 subtract -1 subtract -2 subtract -3 subtract -4 ~省略~ subtract -84 subtract -85 subtract -86 subtract -87 subtract -88 add -89 add -88 add -87 add -86 add -85 ~省略~ add 2 add 3 add 4 add 5 add 6 subtract 7 subtract 6 subtract 5 subtract 4 subtract 3 ~省略~
add() 、 subtract() の内一方の処理が実行されるときにロックがかかるため、同時に実行しているつもりでも add() -> subtract() -> add() のような順番では実行されません。
なお、synchronized を呼び出し元である App.java の add() 、 subtract() につけた場合、 add() の処理が全部終了してから subtract() が実行されることになります。
App.java
~省略~ private static synchronized int add(SampleBehaviour s){ System.out.println("call1 " + Thread.currentThread().getId()); for(int i = 0; i < 10000; i++){ try{ s.add(); } catch(IllegalArgumentException e){ System.out.println(e.getLocalizedMessage()); } } return 0; } private static synchronized int subtract(SampleBehaviour s){ System.out.println("call2 " + Thread.currentThread().getId()); for(int i = 0; i < 10000; i++){ try{ s.subtract(); } catch(IllegalArgumentException e){ System.out.println(e.getLocalizedMessage()); } } return 0; } }
このロックの機構、および処理が同時に実行されない辺りが synchronized が重い、と言われる所以でしょうか。
4.変数を AtomicInteger にした場合
実のところ、現状で今回試しているような複数のスレッドから値の変更をしたい場合、 AtomicInteger を使うのが良さそうです。
理由としてはアトミック性を持ちつつも、 synchronized より速いことが挙げられています。
MultithreadSample.java
package SearchContainedFiles; import java.util.concurrent.atomic.AtomicInteger; class MultithreadSample implements SampleBehaviour{ private AtomicInteger count; public MultithreadSample(){ // ぬるぽ注意. count = new AtomicInteger(0); } public void add(){ System.out.println("add " + count); count.incrementAndGet(); } public void subtract(){ System.out.println("subtract " + count); count.decrementAndGet(); } public int getCount(){ return count.intValue(); } }
実行の結果としては正しく 0 になります。
call1 13 call2 14 call: 0
また、先ほどの synchronized と同じように、 add() 、 subtract() がどのような順番で呼ばれているかを見てみると......
add 0 subtract 0 add 1 subtract 0 add 1 subtract 0 add 1 add 1 add 2 add 3 add 4 add 5 add 6 add 7 add 8 subtract 0 add 9 subtract 8 ~省略~
固まっているところもありますが、 synchronized と比較するとバラけているように見えます。
この AtomicInteger の中身を見てみたいところですが、長くなってきたので一旦切ります。