Не удается получить строку из потока GUI в поток "логики" в Java

Я писал программу, которая просматривает список номеров, чтобы найти те, которые складываются в определенный другой номер. Никаких проблем, алгоритм, хотя и не очень эффективный, но функциональный.

Прямо сейчас список чисел должен быть взят из текстового файла, но я пытался сделать так, чтобы пользователь мог скопировать-вставить список в TextArea, нажать Enter и заставить программу отправить строку обратно нормальный (не GUI) поток.

Для этого я последовал этому примеру (верхний ответ). Я использую ключевое событие вместо нажатия кнопки и строку вместо связанного списка, но в остальном довольно похоже.

Код, где я создаю и запускаю TextDemo (да, я адаптировал учебную программу):

  /*Copy paste text in window */
  public static String copypaste() throws Exception{
    String text = "";
    final TextDemo demo = new TextDemo();
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        demo.createAndShowGUI();
      }
    });
    synchronized(demo.text){
      while(demo.text.equals("")){   //if the window is unused
        demo.text.wait();
      }
      text = demo.text;
    }
    return text;
  }

Сам TextDemo (за исключением отказа от ответственности, пожалуйста, не предупреждайте Oracle:)):

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class TextDemo extends JPanel implements KeyListener{
    protected JTextArea textArea;
    private final static String newline = "\n";
    public String text = "";
    boolean used = false;

    public TextDemo() {
        super(new GridBagLayout());

        textArea = new JTextArea(100, 30);
        textArea.addKeyListener(this);

        textArea.setEditable(true);
        JScrollPane scrollPane = new JScrollPane(textArea);

        //Add Components to this panel.
        GridBagConstraints c = new GridBagConstraints();
        c.gridwidth = GridBagConstraints.REMAINDER;

        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;
        c.weighty = 1.0;
        add(scrollPane, c);
    }

    public void keyPressed(KeyEvent e) {
        // Listen for the key pressed and check it against "Enter"
        // Then read out of our textarea control and print to screen4       
        if (e.getKeyCode() == e.VK_ENTER) {
          synchronized(text){
            text = textArea.getText();
            System.out.println("Text entered.");
            text.notify();
          }
            }
    }

    public void keyReleased(KeyEvent e) {
        // Listen for the key pressed and check it against "Enter"
        // Then read out of our textarea control and print to screen4       
        if (e.getKeyCode() == e.VK_ENTER) {
            //do nothing
        }
    }

    public void keyTyped(KeyEvent e) {
        // Listen for the key pressed and check it against "Enter"
        // Then read out of our textarea control and print to screen4       
        if (e.getKeyCode() == e.VK_ENTER) {
            //do nothing
        }
    }


    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event dispatch thread.
     */
    public static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("TextDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Add contents to the window.
        frame.add(new TextDemo());

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event dispatch thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

}

Когда я запускаю код, кажется, что он работает, пока я не нажму Enter, и моя программа не вылетит. Код ошибки (я включаю только первые 5 строк, полная версия здесь: http://img.photobucket.com/albums/v242/ChaosGuide/illegalmonitorstateexception.png):

Exception in thread "AWT-EventQue-0" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at TextDemo.keyPressed(TextDemo.java:72)
    at java.awt.Component.processKeyEvent(Component.java:6463)
    at javax.swing.JComponent.processKeyEvent(JComponent.java:2829)
    at java.awt.Component.processEvent(Component.java:6282)

Это первый раз, когда я делаю что-либо, что даже касается потоков, поэтому я действительно не понимаю, что я делаю неправильно.

Любая помощь с благодарностью.

1 ответ

Обе темы используют demo.text как их замок, так и зови wait() а также notify() на этом объекте для связи, внутри синхронизированного блока на demo.text, что правильно. Но вы переназначаете новое значение этой переменной непосредственно перед вызовом notify(), Итак, по сути, вы звоните notify() на объекте, у которого нет блокировки:

synchronized(text) { // this block is synchronized on the empty string object
    text = textArea.getText(); // you assign another string to text
    System.out.println("Text entered.");
    text.notify(); // you call notify on this other string object, but you don't own its lock, because you synchronized on the empty string
}

Практическое правило: когда переменная используется в качестве блокировки, она должна быть окончательной, чтобы избежать такого рода ошибок. Более того, использование пустой строки в качестве блокировки - очень плохая идея. Вам лучше создать выделенный объект для этого:

private final Object lock = new Object();

Но лучше всего было бы забыть о wait() а также notify(), которые являются слишком низкоуровневыми, и использовать абстракцию более высокого уровня из пакета java.util.concurrent, например, семафора.

Или, что еще лучше: вместо того, чтобы заставлять основной поток ждать поток диспетчеризации событий, лучше запустить фоновый поток или, скорее, использовать SwingWorker для выполнения длительной операции.

Другие вопросы по тегам