5

マルチスレッドに関して評判が良いので、私はjodaを使用しています。たとえば、すべてのDate / Time / DateTimeオブジェクトを不変にすることで、マルチスレッドの日付処理を効率的にすることができます。

しかし、これは、Jodaが本当に正しいことをしているのかどうかわからない状況です。たぶんそうですが、説明を見てとても興味があります。

DateTimeのtoString()が呼び出されると、Jodaは次のことを行います。

/* org.joda.time.base.AbstractInstant */
public String toString() {
    return ISODateTimeFormat.dateTime().print(this);
}

すべてのフォーマッターはスレッドセーフです(それらも不変です)が、フォーマッターファクトリーについてはどうですか?

private static DateTimeFormatter dt;

/*  org.joda.time.format.ISODateTimeFormat */
public static DateTimeFormatter dateTime() {
    if (dt == null) {
        dt = new DateTimeFormatterBuilder()
            .append(date())
            .append(tTime())
            .toFormatter();
    }
    return dt;
}

これはシングルスレッドアプリケーションでは一般的なパターンですが、マルチスレッド環境ではエラーが発生しやすいことが知られています。

次のような危険があります。

  • nullチェック中の競合状態->最悪の場合:2つのオブジェクトが作成されます。

これは(通常のシングルトンパターンの状況とは異なり)単なるヘルパーオブジェクトであるため、問題ありません。一方はdtに保存され、もう一方は失われ、遅かれ早かれガベージコレクションされます。

  • 静的変数は、オブジェクトの初期化が完了する前に、部分的に構築されたオブジェクトを指している可能性があります

(私をクレイジーと呼ぶ前に、このウィキペディアの記事で同様の状況について読んでください。)

では、Jodaは、部分的に作成されたフォーマッターがこの静的変数で公開されないようにするにはどうすればよいでしょうか。

説明ありがとうございます!

レト

4

4 に答える 4

4

フォーマッターは読み取り専用だとおっしゃいました。最終フィールドのみを使用する場合(私はフォーマッターソースを読みませんでした)、Java言語仕様の第3版では、「最終フィールドセマンティクス」による部分的なオブジェクトの作成から保護されます。私は第2JSLエディションをチェックしませんでしたが、そのエディションでそのような初期化が正しいかどうかはわかりません。

JLSの第17.5章と第17.5.1章を参照してください。必要な発生前の関係のための「イベントチェーン」を構築します。

まず、コンストラクターのどこかに、フォーマッターの最後のフィールドへの書き込みがあります。書き込みwです。コンストラクターが完了すると、「フリーズ」アクションが実行されました。それをfと呼びましょう。プログラム順序のどこか後(コンストラクターから戻った後、おそらく他のメソッドとtoFormatterから戻った後)に、dtフィールドへの書き込みがあります。これに名前を付けましょう。この書き込み(a)は、「プログラム順序」(シングルスレッド実行の順序)でのフリーズアクション(f)の後であるため、JLS定義だけでa(hb(f、a))の前にfが発生します。ふぅ、初期化が完了しました... :)

しばらくして、別のスレッドで、dateTime()。formatの呼び出しが発生します。その時点で、2回の読み取りが必要です。最初の2つは、フォーマッターオブジェクトの最後の変数の読み取りです。それをr2と呼びましょう(JLSとの一貫性を保つため)。2つ目は、フォーマッターの「this」の読み取りです。これは、dtフィールドが読み取られるときにdateTime()メソッドを呼び出すときに発生します。そして、これを読み取りr1と呼びましょう。私たちは今何を持っていますか?読み取りr1は、dtへの書き込みを見ました。その書き込みは前の段落のアクションであると思います(簡単にするために、1つのスレッドだけがそのフィールドを書き込みました)。r1が書き込みaを参照すると、mc(a、r1)(「メモリチェーン」関係、最初の句の定義)があります。現在のスレッドはフォーマッターを初期化せず、アクションr2でフィールドを読み取り、アクションr1で読み取られたフォーマッターの「アドレス」を確認します。したがって、

フリーズする前に、hb(w、f)と書き込みます。dt、hb(f、a)を割り当てる前にフリーズしました。dt、mc(a、r1)からの読み取りがあります。そして、r1とr2の間に間接参照チェーンdereferences(r1、r2)があります。これはすべて、JLSの定義だけで起こる前の関係hb(w、r2)につながります。また、定義により、hb(d、w)ここで、dはオブジェクトの最後のフィールドのデフォルト値の書き込みです。したがって、読み取りr2は書き込みwを認識できず、書き込みr2(プログラムコードからフィールドへの唯一の書き込み)を参照する必要があります。

より間接的なフィールドアクセス(最終フィールドに格納されているオブジェクトの最終フィールドなど)の順序も同じです。

しかし、それだけではありません!部分的に構築されたオブジェクトへのアクセスはありません。しかし、もっと興味深いバグがあります。明示的な同期がない場合、dateTime()はnullを返す可能性があります。実際にはそのような振る舞いは見られないと思いますが、JLS第3版ではそのような振る舞いを防ぐことはできません。メソッドのdtフィールドの最初の読み取りでは、別のスレッドによって初期化された値が表示される場合がありますが、dtの2回目の読み取りでは、「defalut値の書き込み」が表示されます。それを防ぐための関係が存在する前に、何も起こりません。このような可能な動作は第3版に固有であり、第2版には「メインメモリへの書き込み」/「メインメモリからの読み取り」があり、スレッドは変数の値を過去にさかのぼって見ることができません。

于 2010-03-24T19:28:06.277 に答える
0

これは少し答えではありませんが、最も簡単な説明は

では、Jodaは、部分的に作成されていないフォーマッターがこの静的変数で公開されるようにするにはどうすればよいでしょうか。

何も保証されていないだけかもしれません。開発者は、それがバグである可能性があることに気付いていないか、同期する価値がないと感じていました。

于 2010-03-24T17:37:11.657 に答える
0

私は2007年にJodaメーリングリストで同様の質問をしましたが、決定的な答えは見つかりませんでした。その結果、良くも悪くもJodaの時間を避けました。

Java言語仕様のバージョン3は、オブジェクト参照の更新が32ビットか64ビットかに関係なく、アトミックであることを保証します。これを上記の引数と組み合わせると、JodaコードがスレッドセーフなIMOになります(java.sun.com/docs/books/jls/third_edition/html/memory.html#17.7を参照)。

IIRC、バージョン2のJLSには、オブジェクト参照に関する同じ明示的な説明が含まれていませんでした。つまり、32ビット参照のみがアトミックであることが保証されていたため、64ビットJVMを使用している場合は機能する保証はありませんでした。当時、私はJLSv3より前のJava1.4を使用していました。

于 2010-12-13T15:22:39.417 に答える
-1

IMOの最悪のケースは、2つのオブジェクトが作成されるのではなく、いくつか(dateTime()正確には、を呼び出すスレッドの数と同じ数)です。dateTime()同期されておらず、finalでも揮発性でもないためdt、あるスレッドでの値の変更が他のスレッドに表示される保証はありません。したがって、1つのスレッドが初期化された後dtでも、他の任意の数のスレッドが参照をnullと見なす可能性があるため、新しいオブジェクトが作成されます。

それ以外は、他の人が説明しているように、部分的に作成されたオブジェクトはによって公開できませんdateTime()。参照値の更新はアトミックであることが保証されているため、部分的に変更された(=ぶら下がっている)参照もできません。

于 2010-03-24T17:27:44.550 に答える