2011-02-01 74 views
2

我有一些代碼可以做一些初始化(包括製作一個JTextArea對象),啓動三個獨立的線程,然後這些線程嘗試更新JTextArea(即append()),但它根本不工作。 JTextArea沒有任何顯示(但是,在初始化期間,我打印了一些測試線,並且工作正常)。這是怎麼回事?我怎樣才能解決這個問題?此外,每次線程都會隨機調整時間,以便每次更新JTextAreaJTextArea線程安全嗎?

對不起,我沒有提供任何代碼,它的全部分佈在幾個文件。

回答

4

儘管我相信API已經聲明JTextArea#append(...)是線程安全的,但我聽說它有問題,並且會建議僅在EDT上調用它。這個的典型例子是使用SwingWorker並通過調用publish將其附加到process方法中的JTextArea。

對我來說,雖然沒有代碼,但很難向你提出任何具體的建議。我不得不懷疑,如果你讓美國東部時間在你的代碼的某個地方睡覺。

編輯:根據您的評論看看這個教程:Concurrency in Swing


編輯2:Tim Perry按照評論,線程安全,這背後的原因的損失已經張貼在this Java bug並具有做這行代碼,其中的文字添加到JTextArea中的文件:

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

該生產線分解成兩行:

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

的問題是,可能會出現如果文檔,文檔,線1和2之間變化,特別是文獻長度的問題。

+0

你可以提到任何解釋EDT的優秀教程嗎? – 2011-02-01 23:14:01

+0

請參閱我的答案中的編輯。 – 2011-02-01 23:15:32

+0

非常感謝:) – 2011-02-01 23:24:11

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的文件說:

將給定文本追加到 文檔末尾。如果 模型爲空或字符串爲空或 爲空,則不執行任何操作。

這種方法是線程安全的, 雖然大多數Swing方法不是。 請參閱如何使用主題獲取更多 信息。

在JDK7第二部分丟失:

將給定文本追加到的 文檔的結束。如果 模型爲空或字符串爲空或 爲空,則不執行任何操作。

如果你看一下Document接口(其中JTextArea可以使用用戶提供的實例),有沒有辦法在追加即使實現是線程安全的一個線程安全的方式文本。 Swing線程剛剛打破。我強烈建議在走到Swing組件附近的任何地方時嚴格遵守AWT EDT。

2

我相信有經驗的人會相信Document的線程安全。然而,很難相信應用程序如此輕易地利用這個問題,導致JTextArea什麼都沒有顯示。也許使用其他方法,除了append,它們會導致一般故障。我附加了一個測試應用程序,我使用Oracle jre 6運行Debian(也使用java 6 64bit的Win7),並且沒有發現任何問題。

在開發過程中,我不得不修正一些錯誤,其中包括:

  1. 沒有進行同步getLength()insertString()方法,這就造成了誤插位置,甚至BadLocationException秒。其他線程正在修改這兩條指令之間的文件。即使他們在同一條線上:)
  2. 吞嚥BadLocationException。我確定無法擊中它,但是錯了。

實現了getLength()insertString()對創建臨界區的上方,尤其需要後,很明顯,JTextArea會失敗(see Tom Hawtin's answer here)。而且我看到它實際上是這樣做的,因爲不是每個insertString都成功執行,並且結果文本比它應該短。然而,這個問題並沒有發生在循環計數10000,僅在100000處。並且調查jdk 7 code of JTextArea.append,它只做了修改底層文檔,看起來外部同步JTextArea也會這樣。

雖然花費了很長時間才完成,但在0時插入也很好,沒有任何同步。

通常在這樣的應用程序中想要滾動到最後一行。嘿,這是awt。您無法在EDT中使用setCaretPosition。所以我不這樣做。手動滾動。我的建議解決滾動是在another answer

如果你們在你的系統上看到這個應用程序的問題,請評論。我的結論是,Document.insertString是線程安全的,但要有效地使用它,連同getLength,同步是必要的。

在下面的代碼PlainDocument是子類來創建同步append方法,它包裝getLengthinsertString到一個鎖。這個鎖已經保護了訪問權限,所以我不能在沒有單獨的類的情況下使用它。然而外部同步也給出了正確的結果。

順便說一句:對不起,這麼多的編輯。最後,我在學習更多之後重構了這個答案。

代碼:

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); 
     } 
    }); 
    } 
} 
2

JTextArea線程安全的?

絕對不是。不一般。正如其他人所說,即使append方法不再被記錄爲線程安全。但是Java 7 documentaion of AbstractDocument.insertString明確指出這種方法是線程安全的。根據文檔,使用AbstractDocument.insertString似乎是安全的。這是唯一合理的選擇。在gui線程中更新字符串模型將是一個巨大的性能損失。

JTextArea.append怎麼樣?我認爲這取決於底層的Document。對於PlainDocumentDefaultStyledDocument它可能是線程安全的。對於其他型號,應該檢查相關文件。如果不知道底層文檔是什麼,那麼他們應該將append視爲不是線程安全的,並且只能從EDT中調用它。

編輯:另一種可能原因是append不是線程安全的:它由2個操作:getLengthinsertString和2之間,該文件的內容可能改變。所以還要注意像insertString(getLength(), ...)這樣的結構。沒有同步,這是不正確的。 AbstractDocument.writeLock可能會有幫助,但它是受保護的。