Java の同期とパフォーマンスの側面
-
22-08-2019 - |
質問
あるアスペクトで大量のデータ収集コードを同期する必要があることに気づきましたが、パフォーマンスが大きな懸念事項です。パフォーマンスが低下しすぎると、ツールは廃棄されます。int と long を個別に、またさまざまな配列、ArrayList、および Map に書き込みます。私のアスペクトによって選択される関数呼び出しを行うアプリケーションの複数のスレッドが存在します。パフォーマンスに悪影響を及ぼすどのような点に注意する必要がありますか?どのコード パターンがより効率的ですか?
特に、他の多くのデータ記録メソッドを呼び出すメソッドがあります。
void foo() {
bar();
woz();
...
}
このメソッドは主にアスペクト フィールドの増分を追加します。
void bar() {
f++; // f is a field of the aspect
for (int i = 0; i < ary.length; i++) {
// get some values from aspect point cut
if (some condiction) {
ary[i] += someValue; // ary a field of the aspect
}
}
}
foo、bar、woz、その他を個別に同期する必要がありますか、それとも bar、woz などのコードをすべて foo に移動して同期する必要がありますか?同期したほうがいいですか this
, 、特別に作成された同期オブジェクトに対して:
private final Object syncObject = new Object();
(見る これ post)、またはメソッド内の個々のデータ要素に対して:
ArrayList<Integer> a = new ArrayList<Integer>();
void bar() {
synchronize(a) {
// synchronized code
}
}
解決
の同時実行性は非常にトリッキーです。それは間違って取得することは非常に簡単だし、権利を取得するのは非常に難しいです。私はあまりにもひどく、この時点でのパフォーマンスが心配ではありません。私の何よりもまず心配は安全に(何のデッドロックや競合状態)を作業しないように並行コードを取得することになります。
しかし、パフォーマンスの問題について:とき疑いで、プロファイル。これは、異なる同期方式は、パフォーマンスに影響を与えるだろうどれだけ言うのは難しいです。私たちはあなたの提案を与えることはさらに困難です。私たちはあなたのコードの多くのを見ると、アプリケーションはあなたに本当に有益な答えを与えるために何をするかのより深い理解を得るために必要があると思います。これとは対照的に、プロファイリングは、あなたに1つのアプローチは、他のよりも遅い場合にハードな証拠を提供します。それも減速がどこにある特定することができます。
Javaのこれらの日のための偉大なプロファイリングツールがたくさんあります。 NetBeansとEclipseのプロファイラーが良いです。
また、私は完全に離れて、生の同期からの滞在をお勧めしたいです。 java.util.concurrency
パッケージ内のクラスのいくつかを使用してみてください。彼らは、並行コードを書く方がはるかに簡単にする、とはるかに少ないエラーが発生しやすくなります。
また、私はあなたがブライアン・ゲッツらによって実践するにのJava並行処理を読むことをお勧めします。それは非常によく書かれており、地上の多くをカバーしています。
他のヒント
経験則では同期しないことです this
- ほとんどの場合、これはパフォーマンスに影響します。すべてのメソッドは 1 つのオブジェクト上で同期されます。
ロックの使用を検討してください。ロックは非常に優れた抽象化であり、一定期間ロックを試みてから中止するなど、多くの優れた機能を備えています。
if(commandsLock.tryLock(100, TimeUnit.MILLISECONDS)){
try {
//Do something
}finally{
commandsLock.unlock();
}
}else{
//couldnt acquire lock for 100 ms
}
使用についてのセカンドオピニオン java.util.concurrent
. 。2 つのレベルの同期を作成します
- コレクションへのアクセスを同期します (必要な場合)
- フィールドアクセスを同期する
コレクションへのアクセス
あなたのコレクションが read-only
つまり、要素は削除/挿入されません(ただし、要素は変更される可能性があります)。同期されたコレクションを使用する必要があり(ただし、これは必要ない可能性があります...)、反復を同期しないでください。
読み取り専用:
for (int i = 0; i < ary.length; i++) {
// get some values from aspect point cut
if (some condiction) {
ary += someValue; // ary a field of the aspect
}
}
ary は次によって取得されたインスタンスです Collections.synchronizedList
.
読み書き
synchronized(ary){
for (int i = 0; i < ary.length; i++) {
// get some values from aspect point cut
if (some condiction) {
ary += someValue; // ary a field of the aspect
}
}
}
または、いくつかの同時コレクションを使用します (例: CopyOnWriteArrayList)これは本質的に安全です。
主な違いは、最初の読み取り専用バージョンでは、任意の数のスレッドがこのコレクションを反復処理できるのに対し、2 番目のバージョンでは、一度に 1 つのスレッドだけが反復できることです。どちらの場合も、指定されたフィールドをインクリメントする必要があるのは、一度に 1 つのセラッドのみです。
フィールドアクセス
反復の同期とは別に、フィールドの増分を同期します。
のように:
Integer foo = ary.get(ii);
synchronized(foo){
foo++;
}
同期を取り除く
- 同時コレクションを使用します (から
java.util.concurrent
- `Collections.synchronizedXXX' からのものではありません。後者はトラバーサル時に同期する必要があります)。 - 使用
java.util.atomic
これにより、フィールドをアトミックに増加させることができます。
見るべきもの:
Javaメモリモデル - これは、JAVA における同期とデータ調整がどのように機能するかについて非常によく理解できる講演です。
アップデート: 以下を書いて以来、質問が少し更新されているようです。私の無知を許してください--「アスペクト」が何であるかわかりません--しかし、あなたが投稿したサンプルコードから、アトミック/同時コレクションの使用を検討することもできます(例:AtomicInteger、AtomicIntegerArray) または 原子場アップデーター. 。ただし、これはコードの大幅なリファクタリングを意味する可能性があります。(デュアルプロセッサ ハイパースレッディング Xeon 上の Java 5 では、 AtomicIntegerArray のスループット 同期アレイよりも大幅に優れています。申し訳ありませんが、より多くの proc/新しい JVM バージョンでテストを繰り返す段階にはまだ達していません。それ以来、「同期」のパフォーマンスが向上していることに注意してください。)
特定のプログラムに関するより具体的な情報や指標がなければ、できる最善のことはただ単に 適切なプログラム設計に従ってください. 。JVM における同期ロックのパフォーマンスと最適化は、ここ数年間で最も研究と注目を集めている領域 (そうでないとしても) の 1 つであることは注目に値します。したがって、最新バージョンの JVM では、それほど悪いことはありません。
一般的に言えば、 「おかしくなる」ことなく最小限の同期を行う. 。「最小限に」とは、ロックを保持する時間をできるだけ短くし、その特定のロックを使用する必要がある部分だけがその特定のロックを使用するようにすることを意味します。ただし、変更が簡単で、プログラムが依然として正しいことを証明するのが簡単な場合に限ります。たとえば、これを行う代わりに次のようにします。
synchronized (a) {
doSomethingWith(a);
longMethodNothingToDoWithA();
doSomethingWith(a);
}
これを行うことを検討してください もし、そしてその場合に限り あなたのプログラムはまだ正しいでしょう:
synchronized (a) {
doSomethingWith(a);
}
longMethodNothingToDoWithA();
synchronized (a) {
doSomethingWith(a);
}
ただし、不必要にロックが保持された奇妙な単純なフィールド更新では、目に見える違いはほとんどなく、実際にはパフォーマンスが向上する可能性があることを覚えておいてください。場合によっては、ロックを少し長く保持し、ロックの「ハウスキーピング」を減らすことが有益な場合があります。しかし JVM はそれらの決定の一部を行うことができます, ですので、あまり神経質になる必要はありません。一般的に賢明な行動をとれば大丈夫です。
一般に、「独立したプロセス」を形成するメソッド/アクセスのセットごとに個別のロックを設定するようにしてください。それ以外に、別個のロック オブジェクトを用意することも良い方法です。 カプセル化する 使用されるクラス内のロック (すなわち、予想外の方法で外部の呼び出し元によって使用されるのを防ぎます)が、あるオブジェクトを別のオブジェクトをロックとして使用すること自体には、おそらくパフォーマンスの違いはありません(例:あなたが提案しているように、インスタンス自体と、そのクラス内でロックするためだけに宣言されたプライベートオブジェクトを使用します)。ただし、2つのオブジェクトがまったく同じ方法で使用される場合に限ります。
との性能差がなければならない言語構造のライブラリーを内蔵、しかし経験は、それがパフォーマンスになると推測していない私に教えています。
そして、あなたは、ランタイム(負荷型織り)でそれを行うならば、あなたはパフォーマンスヒットが表示されます、基本的にはパフォーマンスヒットを持っていません。
あなたは各側面を持っている場合perinstanceそれは同期の必要性を減らすことができる。
あなたはすべての問題を軽減するために、できるだけ短い時間のために、できるだけ同期を持っている必要があります。
の可能な場合は、任意のデッドロックの問題を軽減するため、可能な限り地元の限りを保ち、スレッド間のできるだけ少ない状態を共有したい場合があります。
詳しい情報は、ところで良い答えにつながります。 :)