web-dev-qa-db-ja.com

WeakReferencesとしてのリスナーの長所と短所

リスナーをWeakReferenceとして保持することの長所と短所は何ですか。

もちろん、大きな「プロ」は次のとおりです。

リスナーをWeakReferenceとして追加すると、リスナーは自分自身を「削除」する必要がなくなります。

更新

オブジェクトへの唯一の参照を持つリスナーを心配している人のために、なぜ2つのメソッド、addListener()とaddWeakRefListener()がないのですか?

削除を気にしない人は後者を使用できます。

70
pdeva

まず、リスナーリストでWeakReferenceを使用すると、オブジェクトに異なるsemanticが与えられ、次にハード参照が使用されます。ハード参照の場合、addListener(...)は、「指定されたオブジェクトに特定のイベントを通知する明示的に停止するまで removeListener(..)で」を意味し、弱参照の場合は、「指定されたオブジェクトに特定のイベントについて通知しますこのオブジェクトが他の人によって使用されなくなるまで(または明示的にremoveListenerで停止します)。多くの場合、オブジェクトを持ち、いくつかのイベントをリッスンし、GCからそれを保持する他の参照がないことは完全に合法です。ロガーはその例です。

ご覧のとおり、WeakReferenceを使用すると、1つの問題を解決するだけでなく(「追加したリスナーをどこかで削除することを忘れないでください」)、別の問題も発生します-「リスナーは、それへの参照がもうない瞬間」。問題を解決するのではなく、ある問題を別の問題と交換するだけです。 とにかくリスナーの寿命を明確に定義、設計、トレースすることを強制しました-何らかの方法で。

したがって、個人的には、リスナーリストでWeakReferenceを使用することは、ソリューションというよりもハッキングのようなものであることに言及することに同意します。知っておく価値のあるパターンです。たとえば、レガシーコードを適切に機能させるために役立つ場合もあります。しかし、それは選択のパターンではありません:)

追伸また、WeakReferenceが追加レベルの間接参照を導入することに注意する必要があります。これにより、場合によっては非常に高いイベントレートでパフォーマンスが低下する可能性があります。

69
BegemoT

これは完全な答えではありませんが、あなたが引用するまさにその強さは、その主な弱点でもあります。アクションリスナーが弱く実装された場合に何が起こるかを検討してください。

button.addActionListener(new ActionListener() {
    // blah
});

そのアクションリスナーは、ガベージコレクションをいつでも取得します!匿名クラスへの唯一の参照が、追加先のイベントであることは珍しくありません。

31
Kirk Woll

リスナーが適切に登録解除されなかったコードを大量に見ました。これは、不要なタスクを実行するためにそれらがまだ不必要に呼び出されたことを意味します。

1つのクラスのみがリスナーに依存している場合は、簡単にクリーニングできますが、25個のクラスがリスナーに依存するとどうなりますか?それらを適切に登録解除することは非常に難しくなります。実際、コードはリスナーを参照する1つのオブジェクトで始まり、25のオブジェクトが同じリスナーを参照する将来のバージョンで終わる可能性があります。

WeakReferenceを使用しないことは、不必要なメモリとCPUを消費する大きなリスクを負うことに相当します。それはより複雑で、よりトリッキーであり、複雑なコード内のハード参照により多くの作業が必要です。

WeakReferencesは、自動的にクリーンアップされるため、長所に満ちています。唯一の欠点は、コードの他の場所にハード参照を保持することを忘れてはならないことです。通常、これはこのリスナーに依存するオブジェクトで発生します。

リスナーの匿名クラスインスタンスを作成するコードは嫌いです(Kirk Wollが述べたように)。一度登録すると、これらのリスナーの登録を解除できなくなるからです。それらへの参照がありません。私見では本当に悪いコーディングです。

リスナーが不要になったら、nullリスナーへの参照を使用することもできます。もう心配する必要はありません。

10

本当に長所はありません。弱参照は通常、ガベージコレクションを防止したくないキャッシュなどの「オプション」データに使用されます。リスナーのガベージを収集したくないので、聴き続けてほしい。

更新:

わかりました、私はあなたが得ているものを理解したかもしれないと思う。短命のリスナーを長寿命のオブジェクトに追加する場合、weakReferenceを使用するとメリットがあります。たとえば、PropertyChangeListenersをドメインオブジェクトに追加して、常に再作成されているGUIの状態を更新する場合、ドメインオブジェクトはGUIを保持するため、ビルドされる可能性があります。 PropertyChangeListenerを介してEmployeeオブジェクトに戻るリスナー参照を使用して、常に再作成される大きなポップアップダイアログを考えてください。間違っている場合は修正してください。しかし、PropertyChangeListenerパターン全体がこれ以上人気があるとは思いません。

一方、GUI要素間でリスナーについて話している場合、またはドメインオブジェクトがGUI要素をリッスンしている場合、GUIがなくなるとリスナーもなくなるため、何も購入しません。

ここにいくつかの興味深い読み物があります:

http://www.javalobby.org/Java/forums/t19468.html

スイングリスナーのメモリリークを解決する方法

5
James Scriven

正直に言うと、私はそのアイデアと、addWeakListenerで何を期待するのかを本当に買いません。たぶんそれは私だけですが、それは間違った良い考えのように見えます。最初は誘惑的ですが、それが示唆する問題は無視できません。

WeakReferenceを使用すると、リスナー自体が参照されなくなったときにリスナーが呼び出されなくなることがわかりません。ガベージコレクターは、数ミリ秒後にメモリを解放することも、まったく解放しないこともできます。これは、リスナーが呼び出されないため、CPUを消費し続け、例外をスローするような奇妙なことを続ける可能性があることを意味します。

スイングの例としては、UIコンポーネントが実際にアクティブウィンドウにアタッチされている場合にのみできることをしようとすることがあります。これにより、例外がスローされ、通知機能がクラッシュして、有効なリスナーが通知されないようにすることができます。

既に述べたように、2番目の問題は匿名リスナーです。すぐに解放される可能性があり、まったく通知されることはありません。

通知の受信を停止すると制御できなくなるため、達成しようとしていることは危険です。それらは永遠に続くか、すぐに止まるかもしれません。

4

WeakReferenceリスナーを追加しているため、カスタムObservableオブジェクトを使用していると思われます。

次の状況では、オブジェクトにWeakReferenceを使用するのが最適です。 -Observableオブジェクトにはリスナーのリストがあります。 -あなたはすでに他の場所のリスナーへのハードリファレンスを持っています。 (これを確認する必要があります)-Observableに参照があるからといって、ガベージコレクターがリスナーのクリアを停止しないようにします。 -ガベージコレクション中に、リスナーがクリアされます。リスナーに通知するメソッドでは、通知リストからWeakReferenceオブジェクトを消去します。

3
Deepak

私の意見では、ほとんどの場合、それは良い考えです。リスナーのリリースを担当するコードは、登録された場所と同じ場所にあります。

実際には、リスナーを永遠に維持しているソフトウェアがたくさんあります。多くの場合、プログラマーは登録を解除する必要があることを認識していません。

通常、いつ登録を解除するかを操作できるリスナーへの参照を含むカスタムオブジェクトを返すことができます。例えば:

listeners.on("change", new Runnable() {
  public void run() {
    System.out.println("hello!");
  }
}).keepFor(someInstance).keepFor(otherInstance);

このコードは、リスナーを登録し、リスナーをカプセル化し、インスタンスパラメーターをキーとして静的なweakHashMapにリスナーを追加するkeepForメソッドを持つオブジェクトを返します。これにより、少なくともsomeInstanceおよびotherInstanceがガベージコレクションされない限り、リスナーが登録されます。

KeepForever()またはkeepUntilCalled(5)またはkeepUntil(DateTime.now()。plusSeconds(5))またはunregisterNow()などの他のメソッドがあります。

デフォルトは永久に保持できます(未登録まで)。

これは、弱参照なしで実装することもできますが、リスナーの削除をトリガーするファントム参照です。

編集:このアプローチの基本バージョンを実装する小さなライブラリを作成しました https://github.com/creichlin/struwwel

2
Christian

WeakReferencesをリスナーに使用するための正当なユースケースは、次のGCサイクルの後に明示的に存在してはならないリスナーが何らかの形で使用されている場合を除き、考えられません(もちろん、ユースケースはVM /プラットフォーム固有です)。

SoftReferencesのもう少し正当なユースケースを想定することは可能です。リスナーはオプションですが、多くのヒープを占有し、空きヒープサイズが危険にさらされ始めたときに最初に行くべきです。ある種のオプションのキャッシングまたは他のタイプの補助リスナーが、候補になると思います。その場合でも、リスナーの内部はSoftReferencesを使用する必要があり、リスナーと受信者の間のリンクは使用しないように思われます。

通常、永続的なリスナーパターンを使用している場合、リスナーはオプションではないため、この質問をすることは、アーキテクチャを再検討する必要がある症状である可能性があります。

これは学術的な質問ですか、それともあなたが対処しようとしている実際的な状況がありますか?それが実際的な状況である場合、私はそれが何であるかを聞きたいです-そして、おそらくあなたはそれを解決する方法についてより多くの、より少ない抽象的なアドバイスを得ることができます。

2
jkraybill

オリジナルのポスターについて3つの提案があります。古いスレッドを復活させてすみませんが、このスレッドでは以前に私の解決策が議論されていなかったと思います。

まず、JavaFXライブラリのjavafx.beans.values.WeakChangeListenerの例に従うことを検討してください。

次に、ObservableのaddListenerメソッドを変更して、JavaFXパターンをアップしました。新しいaddListener()メソッドは、対応するWeakXxxListenerクラスのインスタンスを作成します。

"fire event"メソッドは、XxxWeakListenersを間接参照し、WeakReference.get()がnullを返したときにそれらを削除するように簡単に修正されました。

リスト全体を繰り返す必要があるため、removeメソッドは少し厄介になりました。つまり、同期を行う必要があります。

第三に、この戦略を実装する前に、私はあなたが役に立つと思うかもしれない別の方法を採用しました。 (ハードリファレンス)リスナーは、まだ使用されているかどうかを確認する新しいイベントを取得しました。そうでない場合は、オブザーバーからサブスクライブ解除され、GCが許可されます。長寿命のObservableにサブスクライブしている短命のリスナーの場合、陳腐化の検出はかなり簡単でした。

「リスナーを常にサブスクライブ解除するのが良いプログラミング慣行であると規定した人々に敬意を表して、リスナーがサブスクライブを解除するたびに、ログエントリを作成し、コードの問題を後で修正するようにしました。

1
Teto

WeakListenerは、GCがリスナーの存続期間を制御することを特に望む状況で役立ちます。

前述のように、これは通常のaddListener/removeListenerの場合と比較して実際には異なるセマンティクスですが、一部のシナリオでは有効です。

たとえば、非常に大きなツリーを考えてみましょう。これはまばらです-一部のレベルのノードは明示的に定義されていませんが、階層の上位の親ノードから推測できます。暗黙的に定義されたノードは、定義されたそれらの親ノードをリッスンするため、それらは暗黙/継承された値を最新に保ちます。しかし、ツリーは巨大です-暗黙のノードが永遠に存在することを望みません-呼び出し元のコードで使用されている限り、さらに同じ値が何度も繰り返されることを避けるために、おそらく数秒のLRUキャッシュが必要です。

ここでは、弱いリスナーにより、子ノードが親に耳を傾けることができますが、その有効期間は到達可能性/キャッシュによって決定されるため、構造はメモリ内のすべての暗黙ノードを維持しません。

0
mdma

テストプログラムからは、匿名のActionListenerはオブジェクトのガベージコレクションを妨げないようです。

import Java.awt.event.ActionEvent;
import Java.awt.event.ActionListener;

import javax.swing.JButton;

public class ListenerGC {

private static ActionListener al = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.err.println("blah blah");
        }
    };
public static void main(String[] args) throws InterruptedException {

    {
        NoisyButton sec = new NoisyButton("second");
        sec.addActionListener(al);
        new NoisyButton("first");
        //sec.removeActionListener(al);
        sec = null;
    }
    System.out.println("start collect");
    System.gc( );
    System.out.println("end collect");
    Thread.sleep(1000);
    System.out.println("end program");
}

private static class NoisyButton extends JButton {
    private static final long serialVersionUID = 1L;
    private final String name;

    public NoisyButton(String name) {
        super();
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println(name + " finalized");
        super.finalize();
    }
}
}

生成するもの:

start collect
end collect
first finalized
second finalized
end program
0
gerardw

また、毎回呼び出されることが保証されていない場所で登録を解除する場合は、WeakReferenceを使用してリスナーを実装する必要があります。

ListViewの行ビュー内で使用されたカスタムPropertyChangeSupportリスナーの1つに問題があったことを思い出すようです。これらのリスナーを登録解除する素敵で信頼できる方法を見つけることができなかったため、WeakReferenceリスナーを使用することが最もクリーンなソリューションのように思えました。

0
Dan J