Swingライブラリにスレッドセーフがないという批判をよく耳にします。それでも、自分のコードで何をするのか、問題が発生する可能性があるかどうかはわかりません。
Swingがスレッドセーフではないという事実はどのような状況で発生しますか?
積極的に避けるべきことは何ですか?
Swingライブラリにスレッドセーフがないという批判をよく耳にします。それでも、自分のコードで何をするのか、問題が発生する可能性があるかどうかはわかりません。
Swingがスレッドセーフではないという事実はどのような状況で発生しますか?
積極的に避けるべきことは何ですか?
これらはイベント スレッド上にあるため、ボタンやイベントなどに応答して長時間実行されるタスクを実行しないでください。イベント スレッドをブロックすると、GUI 全体が完全に応答しなくなり、ユーザーは本当に腹を立てます。これが、Swing が遅くて無愛想に見える理由です。
Threads、Executors、および SwingWorker を使用して、EDT 以外でタスクを実行します (イベント ディスパッチ スレッド)。
EDT の外部でウィジェットを更新または作成しないでください。EDT の外部で実行できる呼び出しは、Component.repaint() だけです。SwingUtilitis.invokeLater を使用して、特定のコードが EDT で実行されるようにします。
EDT Debug Techniquesとスマートなルック アンド フィール ( EDT 違反をチェックするSubstanceなど) を使用する
これらのルールに従えば、Swing は非常に魅力的でレスポンシブな GUI を作成できます。
本当にすばらしい Swing UI の例: Palantir Technologies . 注:私は彼らのために働いていません.素晴らしいスイングの例にすぎません. 残念ながら公開デモはありません...彼らのブログも良いです、まばらですが、良いです
これは、Swing に関する Robinson & Vorobiev の本を購入してよかったと思う質問の 1 つです。
a の状態にアクセスするものjava.awt.Component
は すべてEDT 内で実行する必要がありますが、3 つの例外repaint()
がrevalidate()
ありinvalidate()
ます。まだ実現されていない UI の任意のコンポーネント。start()
およびそのアプレットが呼び出される前のアプレット内の任意のコンポーネント。
スレッドセーフに特別に作成されたメソッドはあまり一般的ではないため、多くの場合、スレッドセーフであるものを覚えておくだけで十分です。通常は、そのようなメソッドがないと仮定して問題を解決することもできます (たとえば、SwingWorker で再描画呼び出しをラップすることは完全に安全です)。
実現setVisible(true)
とは、コンポーネントが、 、show()
、またはのいずれかが呼び出された最上位のコンテナ (JFrame など) であるかpack()
、実現されたコンポーネントに追加されていることを意味します。これは、多くのチュートリアルの例がそうであるように、UI を main() メソッドで構築してもまったく問題ないことを意味します。なぜなら、setVisible(true)
すべてのコンポーネントが追加され、フォントと境界線が設定されるなどするまで、最上位のコンテナを呼び出さないからです。
同様の理由で、メソッド内でアプレット UI を構築し、すべての構築後にinit()
呼び出すことは完全に安全です。start()
送信先の Runnables で後続のコンポーネントの変更をラップするinvokeLater()
と、数回実行するだけで簡単に正しく取得できます。私が面倒だと思うことの 1 つは、someTextField.getText()
別のスレッドからコンポーネント (たとえば ) の状態を読み取ることです。技術的には、これも でラップする必要がinvokeLater()
あります。実際には、コードが見苦しく速くなる可能性がありますが、私は気にしないか、最初のイベント処理時にその情報を取得するように注意しています (通常、ほとんどの場合、それを行うのに適切な時期です)。
Swing がスレッドセーフではないというだけでなく (ほとんどそうではありません)、スレッドに対して敵対的です。単一のスレッド (EDT 以外) で Swing の処理を開始した場合、Swing が EDT に切り替わる場合 (文書化されていません)、スレッドセーフの問題が発生する可能性があります。スレッドセーフを目指している Swing テキストでさえ、有用なスレッドセーフではありません (たとえば、ドキュメントに追加するには、最初に長さを見つける必要があり、挿入前に変更される可能性があります)。
そのため、すべての Swing 操作を EDT で行います。EDT はメインが呼び出されるスレッドではないことに注意してください。そのため、次のボイラープレートのような (単純な) Swing アプリケーションを開始します。
class MyApp {
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() { public void run() {
runEDT();
}});
}
private static void runEDT() {
assert java.awt.EventQueue.isDispatchThread();
...
物質のようなインテリジェントスキンを使用する代わりに、次のユーティリティメソッドを作成することもできます。
public final static void checkOnEventDispatchThread() {
if (!SwingUtilities.isEventDispatchThread()) {
throw new RuntimeException("This method can only be run on the EDT");
}
}
イベントディスパッチスレッド上にある必要がある、作成するすべてのメソッドで呼び出します。これの利点は、システム全体のチェックを非常に迅速に無効化および有効化することです。たとえば、本番環境でこれを削除する場合があります。
もちろん、インテリジェントスキンはこれだけでなく追加のカバレッジも提供できることに注意してください。
イベントをディスパッチするスレッド以外では、Swing の作業を一切行わないようにしてください。Swing は簡単に拡張できるように作成されており、Sun はこれにはシングルスレッド モデルの方が適していると判断しました。
上記のアドバイスに従っている間、問題はありませんでした。他のスレッドから「スイング」できる状況がいくつかありますが、私はその必要性を見つけたことはありません。
Java 6 を使用している場合は、間違いなく SwingWorker がこれに対処する最も簡単な方法です。
基本的に、UI を変更するものはすべて EventDispatchThread で実行されるようにする必要があります。
これは、SwingUtilities.isEventDispatchThread() メソッドを使用して、その中にいるかどうかを確認することで見つけることができます (一般的には良い考えではありません。どのスレッドがアクティブであるかを知っておく必要があります)。
EDT を使用していない場合は、SwingUtilities.invokeLater() および SwingUtilities.invokeAndWait() を使用して、EDT で Runnable を呼び出します。
EDT 以外で UI を更新すると、信じられないほど奇妙な動作が発生します。個人的には、これを Swing の欠陥とは考えていません。UI の更新を提供するためにすべてのスレッドを同期する必要がないため、効率が向上します。その点を覚えておく必要があります。
The phrase 'thread-unsafe' sounds like there is something inherently bad (you know... 'safe' - good; 'unsafe' - bad). The reality is that thread safety comes at a cost - threadsafe objects are often way more complex to implement (and Swing is complex enough even as it is.)
Also, thread-safety is achieved either using locking (slow) or compare-and-swap (complex) strategies. Given that the GUI interfaces with humans, which tend to be unpredictable and difficult to synchronize, many toolkits have decided to channel all events through a single event pump. This is true for Windows, Swing, SWT, GTK and probably others. Actually I don't know a single GUI toolkit which is truly thread-safe (meaning that you can manipulate its objects' internal state from any thread).
What is usually done instead is that the GUIs provide a way to cope with the thread-unsafety. As others noted, Swing has always provided the somewhat simplistic SwingUtilities.invokeLater(). Java 6 includes the excellent SwingWorker (available for previous versions from Swinglabs.org). There are also third party libraries like Foxtrot for managing threads in Swing context.
The notoriety of Swing is because the designers have taken light handed approach of assuming that the developer will do the right thing and not stall the EDT or modify components from outside the EDT. They have stated their threading policy loud and clear and it's up to the developers to follow it.
It's trivial to make each swing API to post a job to the EDT for each property-set, invalidate, etc., which would make it threadsafe, but at the cost of massive slowdowns. You can even do it yourself using AOP. For comparison, SWT throws exceptions when a component is accessed from a wrong thread.
EDT以外のスレッドからGUIコンポーネントと対話する場合は、invokeLater()とinvokeAndWait()を実際に使用する必要があります。
開発中には機能する可能性がありますが、ほとんどの同時バグと同様に、完全に無関係に見え、不確定に発生する奇妙な例外が発生し始めます。通常、実際のユーザーが出荷した後に発見されます。良くない。
また、アプリが今後ますます多くのコアを搭載したCPUで動作し続けるという確信はありません。コアは、OSによってシミュレートされるだけでなく、真に同時実行されるため、奇妙なスレッドの問題が発生する可能性が高くなります。
はい、RunnableインスタンスのEDTにすべてのメソッド呼び出しをラップバックするのは醜いですが、それはあなたにとってJavaです。私たちが閉鎖されるまで、あなたはそれと一緒に暮らす必要があります。
スレッド化の詳細については、Allen Holub による Taming Java Threads が古い本ですが、よく読んでください。
Holub は、レスポンシブ UI を実際に促進し、例と問題を軽減する方法を詳しく説明しています。
http://www.amazon.com/Taming-Java-Threads-Allen-Holub/dp/1893115100 http://www.holub.com/software/taming.java.threads.html
最後の「If I was king」セクションが大好きです。
これは、swing をスレッドフレンドリーにするためのパターンです。
Sublas Action (MyAction) を実行し、doAction スレッド化します。コンストラクターが文字列 NAME を取るようにします。
抽象 actionImpl() メソッドを与えます。
次のようにします.. (疑似コード警告!)
doAction(){
new Thread(){
public void run(){
//kick off thread to do actionImpl().
actionImpl();
MyAction.this.interrupt();
}.start(); // use a worker pool if you care about garbage.
try {
sleep(300);
Go to a busy cursor
sleep(600);
Show a busy dialog(Name) // name comes in handy here
} catch( interrupted exception){
show normal cursor
}
タスクにかかった時間を記録することができ、次回はダイアログで適切な見積もりを表示できます。
本当に親切にしたい場合は、別のワーカースレッドでもスリープを行ってください。
モデル インターフェイスでさえスレッド セーフではないことに注意してください。サイズとコンテンツは個別の get メソッドでクエリされるため、それらを同期する方法はありません。
別のスレッドからモデルの状態を更新すると、少なくともサイズがまだ大きい (テーブルの行はまだ配置されている) 状況を描画できますが、コンテンツは存在しなくなります。
常に EDT でモデルの状態を更新すると、これらを回避できます。