2011-02-01 10 views
2

私はいくつかの初期化(JTextAreaオブジェクトの作成を含む)を行い、3つの別々のスレッドを開始してから、JTextArea(つまりappend())を更新しようとします。 JTextAreaには何も表示されません(ただし、初期化中にいくつかのテスト行が印刷され、正常に動作します)。どうしたの?これをどうすれば解決できますか?また、これらのスレッドはそれぞれ、JTextAreaを更新する必要があるたびにランダムな時間をスリープします。JTextAreaスレッドセーフ?

申し訳ありませんが、私はコードを提供していない、そのすべてがいくつかのファイルに広がっています。

答えて

4

私は、APIがJTextArea#append(...)がスレッドセーフであると述べていると思いますが、私は問題について聞いたことがあり、これはEDTでのみ呼び出されることを推奨します。これの古典的な例は、SwingWorkerを使用し、publishを呼び出してprocessメソッドでJTextAreaを追加することです。

私にとって、コードなしであなたに具体的な提案をするのは難しいでしょう。 EDTをあなたのコードのどこかに寝かせているのであれば、私は思っています。

編集:編集2 Concurrency in Swing


:あなたのコメントどおり、このチュートリアルをチェックアウトTim Perryによってコメントのとおり、この背後にあるスレッドの安全性と推論の損失はthis Java bugに掲示し、に持っているされています

:行が2行に分解し

doc.insertString(doc.getLength(), str, null); 

:テキストはJTextAreaにのドキュメントに追加されたコード行を行います

  1. int len=doc.getLength();
  2. doc.insertString(len,str,null);

問題は、問題がライン1と2の間にあればドキュメント、ドキュメント、変更、特に文書の長さを生じ得ることです。

+0

EDTについて説明する良いチュートリアルはありますか? –

+0

私の答えの編集を参照してください。 –

+0

ありがとうございました:) –

2

JTextArea.append(..)はスレッドセーフなので、別のスレッドから呼び出しても安全です。

しかし.append()状態のjavadocは:

だから、
Does nothing if the model is null or the string is null or empty. 

は、(適切なコンストラクタを介して)JTextAreaのモデルが初期化されていることを確認してください。

3

は、Java 1.6では、JTextArea.appendのドキュメントは言う:

が 文書の末尾に、指定されたテキストを追加します。 モデルがnullまたは文字列がnullまたは が空の場合は何もしません。

このメソッドは、ほとんどのSwingメソッドではありませんが、 スレッドセーフです。 の詳細については、スレッドを使用する方法を参照してください。JDK7で

第二の部分は欠落しています。

は ドキュメントの末尾に、指定されたテキストを追加。 モデルがnullまたは文字列がnullまたは が空の場合は何もしません。

あなたは(JTextAreaは、ユーザ供給のインスタンスを使用することができます)Documentインターフェースを見れば、実装はスレッドセーフであっても、スレッドセーフな方法でテキストを追加する方法はありません。スイングスレッドはちょうど壊れています。私は、Swingコンポーネントの近くのどこに行っても、AWT EDTに固執することを強くお勧めします。

+0

私はこのことを理解することに問題がありました。実装がスレッドセーフなものであっても、スレッドセーフな方法でテキストを追加する方法はないので、それについて詳しく説明します。私はあなたが意味するように:ドキュメントのインタフェースにはアトミックな* append *演算がないので、実際には2つの後続の呼び出し、すなわち 'getLength'、' insertString'を追加する必要があります。これらの2つの呼び出しの間で内容が変化しないことを保証する方法はありません。 – Jarekczek

2

私はDocumentのスレッドの安全性を信じることについて警告する経験豊かな人を信頼します。しかし、アプリケーションがこの問題を簡単に悪用すると信じるのは難しいので、何も表示しないでください。JTextAreaおそらく、append以外のいくつかの方法が使用され、一般的な障害が発生する可能性があります。私はDebian上でOracle jre 6(そしてJava 6 64bitでWin7)を実行しても問題ないと思うテストアプリケーションを添付します。

  1. を誤挿入配置とさえBadLocationException Sの結果getLength()insertString()方法を同期しない:私は含まれいくつかのミスを修正した開発プロセスの間に

    。他のスレッドは、これらの2つの命令の間で文書を修正していました。たとえ同じライン上にあったとしても:

  2. 嚥下BadLocationException。私はそれを打つことは不可能だと確信していたが、間違っていた。

getLength()insertString()ペアのためのクリティカルセクションを作成する上で、特に必要性を実現した後、それは(see Tom Hawtin's answer hereJTextAreaが失敗することは明らかです。そして、実際には、insertStringがすべて正常に実行されたわけではなく、結果として得られるテキストが、それよりも短くなっていたからです。しかし、この問題はループカウント10000では100000でしか発生しませんでした。jdk 7 code of JTextArea.appendを調べてみると、基礎となる文書を変更するだけで、外部で同期するのはJTextAreaとなります。

0に挿入すると、同期することなく正常に動作しましたが、完了までに時間がかかりました。

通常、このようなアプリケーションでは、最後の行までスクロールします。ねえ、これはまあまあです。あなたはEDTのうちsetCaretPositionできません。だから私はしません。手動でスクロールします。スクロールを解決するための私の提案はanother answerです。

あなたのシステムでこのアプリケーションの問題が発生した場合は、コメントしてください。私の結論はDocument.insertStringはスレッドセーフですが、効果的に使用するにはgetLengthと一緒に同期が必要です。次のコードPlainDocumentにおいて

は、ロックにgetLengthinsertStringをラップ同期append方法を作成するためにサブクラス化されています。このロックは保護されたアクセス権を持っているため、別のクラスなしでは使用できませんでした。しかし、外部同期によっても正しい結果が得られました。

ご参考までに非常に多くの編集がありました。最後に、より多くのことを学んだ後、この答えを再構成しました。

コード:

import java.awt.*; 
import java.util.concurrent.CountDownLatch; 
import javax.swing.*; 
import javax.swing.text.*; 

class SafePlainDocument extends PlainDocument 
{ 
    public void append(String s) 
    { 
    writeLock(); 
    try { 
     insertString(getLength(), s, null); 
    } 
    catch (BadLocationException e) { 
     e.printStackTrace(); 
    } 
    finally 
    { 
     writeUnlock(); 
    } 
    } 
} 

public class StressJText 
{ 
    public static CountDownLatch m_latch; 
    public static SafePlainDocument m_doc; 
    public static JTextArea m_ta; 

    static class MyThread extends Thread 
    { 
    SafePlainDocument m_doc; 
    JTextArea m_ta; 

    public MyThread(SafePlainDocument doc) 
    { 
     m_doc = doc; 
    } 

    public void run() 
    { 
     for (int i=1; i<=100000; i++) { 
     String s = String.format("%19s %9d\n", getName(), i); 
     m_doc.append(s); 
     } 
     StressJText.m_latch.countDown(); 
    } 
    } 

    public static void main(String sArgs[]) 
    { 
    System.out.println("hello"); 
    final int cThreads = 5; 
    m_latch = new CountDownLatch(cThreads); 
    java.awt.EventQueue.invokeLater(new Runnable() { 
     public void run() { 
      JFrame frame = new JFrame(); 
      m_ta = new JTextArea(); 
      m_doc = new SafePlainDocument(); 
      m_ta.setDocument(m_doc); 
      m_ta.setColumns(50); 
      m_ta.setRows(20); 
      JScrollPane scrollPane = new javax.swing.JScrollPane(); 
      scrollPane.setViewportView(m_ta); 
      frame.add(scrollPane); 
      frame.pack(); 
      frame.setVisible(true); 

      for (int it=1; it<=cThreads; it++) { 
      MyThread t = new MyThread(m_doc); 
      t.start(); 
      } 
     } 
    }); 
    try { 
     m_latch.await(); 
    } 
    catch (InterruptedException ie) { 
     ie.printStackTrace(); 
    } 
    java.awt.EventQueue.invokeLater(new Runnable() { 
     public void run() { 
      System.out.println("tf len: " + m_ta.getText().length()); 
      System.out.println("doc len: " + m_doc.getLength()); 
      System.exit(0); 
     } 
    }); 
    } 
} 
+0

+1あなたの強靭さに感銘を受けました:-)あなたはどこかで私を失ってしまいましたが(私のせいで、これを掘り起こすインセンティブがないので私のせいではありません) - 通常、私はEDTのスイング関連のすべてにアクセスし、ハッピー。キャレットについては、DefaultCaretにはupdatedPolicy(またはそれに類するもの、検索するには怠惰なもの...咳)というプロパティがあります。これはほとんどのユースケースで十分です。 – kleopatra

+0

@Kleopatra、ありがとう!あなたは私に重要なことを思い出させました。それは何のためですか? **効率**。印象的ではありませんが、これは結果です(Linuxでは10000ループ):作業スレッド(上記のコードのように):** 10s **、invokeLater:** 15,5s **、invokeAndWait:59,5s。 'invokeLater'は約50%のパフォーマンス低下しかないので、私はあなたと一緒です!副作用:EDTソリューションは自動的にキャレットを更新します。 – Jarekczek

+0

re:最後の行にスクロールする:これは自動的に実行されます。ここでデフォルトのキャレット更新ポリシーを確認してください:http://docs.oracle.com/javase/8/docs/api/javax/swing/text/DefaultCaret.html#setUpdatePolicy-int- –

2

JTextArea、スレッドセーフ?

間違いなく。一般的ではありません。他の人が述べたように、appendメソッドはもはやスレッドセーフであると文書化されていません。しかし、Java 7 documentaion of AbstractDocument.insertStringは、このメソッドがスレッドセーフであることを明確に述べています。

AbstractDocument.insertStringを使用すると安全だと思われます。それが唯一の合理的な選択肢です。 GUIスレッドで文字列モデルを更新すると、パフォーマンスが大幅に低下します。

JTextArea.append?私はそれが基礎となるDocumentに依存すると仮定します。 PlainDocumentDefaultStyledDocumentについては、スレッドセーフである可能性があります。他のモデルの場合は、関連するドキュメントを調べる必要があります。基礎となる文書が何であるかわからない場合は、appendをスレッドセーフではないものとして扱い、EDTからのみ呼び出す必要があります。

編集:appendがスレッドセーフではない別の理由:getLengthinsertStringの2つの操作で構成され、2の間で文書の内容が変更されることがあります。したがって、insertString(getLength(), ...)のような構文も注意してください。同期がないと正しくありません。 AbstractDocument.writeLockが役に立っていますが、保護されています。