2011-02-03 55 views
2

我有一個非常簡單的應用程序,帶有非常奇怪的行爲。在啓動線程或執行計算的SwingWorker時擺動僞塊

它本質上是SwingWorker的例子,但是當我按下按鈕時,GUI的行爲就像EDT被阻塞一樣。我可以同時啓動兩個並行運行(運行時間幾乎相同),但菜單在運行時仍會凍結。當我使用可運行的線程時,會發生完全相同的行爲。另外有趣的是,如果循環被Thread.sleep替換,則GUI的行爲正確。

任何想法?

public class DummyFrame extends JFrame { 

     public DummyFrame() { 
       JMenuBar bar = new JMenuBar(); 
       JMenu menu = new JMenu("File"); 
       menu.add(new JMenuItem("TEST1")); 
       menu.add(new JMenuItem("Test2")); 
       bar.add(menu); 
       setJMenuBar(bar); 

       JButton button = new JButton("FOOBAR"); 
       button.addActionListener(new ActionListener() { 
         @Override 
         public void actionPerformed(ActionEvent arg0) { 
           final long start = System.currentTimeMillis(); 

           SwingWorker<Void, Integer> testTask = new SwingWorker<Void, Integer>() {   
             @Override 
             protected Void doInBackground() 
             throws Exception { 
               int k = 0; 
               for (int i=0; i<200000; i++) { 
                 for (int j=0; j<100000; j++) { 
                   if (i==j && i%10000 == 0) 
                     k++; 
                 } 
               } 
               System.out.println(k+" "+(System.currentTimeMillis()-start)); 
               return null; 
             } 
           }; 
           testTask.execute(); 
         } 

       }); 
       getContentPane().add(button); 
       pack(); 
     } 

     public static void main(String[] args) { 
       SwingUtilities.invokeLater(new Runnable() { 
         public void run() { 
           DummyFrame f = new DummyFrame(); 
           f.setVisible(true);  
         } 
       }); 
     } 
} 

回答

2

問題出在VM的線程實現上。規範沒有具體說明如何完成。 Java線程應該映射到本機Windows線程,然後使用Windows調度程序共享時間片。目前還不清楚這是否正在進行,所有官方文檔僅支持在Solaris上運行的線程信息。

我認爲主要問題來自線程搶佔的實現細節。這可能是由JVM和本機操作系統之間編譯和搶佔控制的代碼優化組合引起的。 JVM的可以使用方法調用作爲搶佔線程的點,我認爲這裏的問題的一部分是兩個循環,你可以調用另一個循環。如果你用函數調用分解它,它會在我的機器上表現得更好。我使用的是1.6.0_23 64位服務器VM在Windows 7

SwingWorker<Void, Integer> testTask = new SwingWorker<Void, Integer>() { 

    private int k; 

    private void inc() { 
     this.k++; 
    } 

    private void innerLoop(int i) { 
     for (int j=0; j<100000; j++) { 
      if (i==j && i%10000 == 0) 
       this.inc(); 
     } 
    } 

    @Override 
    protected Void doInBackground() 
    throws Exception { 
     System.out.println("Started"); 
     for (int i=0; i<200000; i++) { 
      this.innerLoop(i); 
     } 
     System.out.println(k+" "+(System.currentTimeMillis()-start)); 
     return null; 
    } 
}; 

即使這有幾個開始他們在後一次的問題,但是。最好的解決方案是每次啓動內部循環時都要呼叫Thread.yield()。這可確保編譯後的代碼爲調度程序提供每次迭代搶佔線程的機會。

0

我認爲問題在於代碼可以如此輕鬆地使用100%的CPU。如果每個內核都有一個線程運行,那麼其他任何地方都沒有太多空間。

+1

更具體地說,我認爲你的JVM進程中有一個線程運行,這就是爲什麼它不一定會使整個系統癱瘓 - 只是運行在該進程中的JVM。即使你不使用`SwingWorker` - 只需使用一個普通的`Thread` - 你可能會遇到同樣的問題。處理這個問題的正確方法是在緊密循環內調用Thread.yield(),這將使其他線程有機會運行。 – 2011-02-03 01:28:50