Search on the blog

2012年8月12日日曜日

Reading Java RunTime Source Code (3)

java.util.Stackのソースを読みました。実装は非常にシンプルで分かりやすかったです。気になったところだけ以下にまとめます。

class Stack<E> extends Vector<E>
Eはジェネリクスの型変数の宣言。クラス宣言部を見て分かるように、StackはVectorを継承しています。

synchronized
Stackでは以下のメソッドが定義されています。
  • push
  • pop
  • peek
  • empty
  • search
Stackから連想する処理は一通り実装されています。気になったのは、synchronized。赤字のメソッドのみsynchronized句が指定されています。synchronizedする/しないの違いが気になったので、考えてみました。

public synchronized E pop() {
    E obj;
    int len = size();
    
    obj = peek();
    removeElementAt(len - 1);

    return obj;
}

public synchronized E peek() {
    int len = size();

    if (len == 0)
        throw new EmptyStackException();
    
    return elementAt(len - 1);
}

まずpopですが、3行目と6行目の間で別のスレッドがpopを実行した場合に、ArrayIndexOutOfBoundsExceptionが発生する可能性があります。これを防止するためにオブジェクトにロックをかけていると考えられます。同様にpeekの場合も2行目と7行目の間に別スレッドからpopを実行されるとArrayIndexOutOfBoundsExceptionが発生する可能性があります。

searchは少し違います。ArrayIndexOutOfBoundsExceptionがthrowされそうな箇所は見当たりません。
public synchronized int search(Object o) {
    int i = lastIndexOf(o);
    
    if (i >= 0) {
        return size() - i;
    }
    return -1;
}
searchの仕様は、対象のオブジェクトがStack内に存在すればそのindexを1-basedで返します。存在しなければ-1を返します。もし、2行目と5行目の間にpopが別スレッドから実行されると、仕様と異なる値(0, -2, -3, ..)が返される可能性があります。これを防止するためにsynchronizedを設定していると考えられます。ただし、pushにはsynchronizedが指定されていないので、searchの実行中に別スレッドからpushが実行される可能性はあります。(スレッドがオブジェクトのロックを気にするのは、synchronizedブロックを実行しようとする場合のみなので。)しかし、これは返されるindexが1インクリメントされるだけなので問題はないです。

synchronizedされていないpush、emptyについては上記で見てきたような問題とは無関係ですし、特に他に同期させるべき場面も思い浮かばないので、無くてよいのかなぁという感じです。簡単に必要ないと言いましたが、本来は、他の処理から割り込ませないような場面がないことと、他のsynchronizedが付いている処理を邪魔する可能性がないことを保証した上で、synchronizedは付けなくていいという結論を出さないといけないんですよね。マルチスレッドを考慮したクラス設計は難しいですね。。

0 件のコメント:

コメントを投稿