2013-04-26 4 views
0

Я программирую сетевой одноэлементный класс, и он должен запускаться в потоке, до сих пор нет проблем, однако я не могу заставить его работать печально.java singleton network реализация runnable

Сеть Класс:

public class Network extends Thread {  
    private static Network cachedInstance = new Network(); 

    private PrintWriter out; 
    private BufferedReader in; 

    private Network() {   
    } 

    private void init() { 
     try { 
      Socket clientSocket = new Socket(Config.HOST_NAME, Config.HOST_PORT); 
      out = new PrintWriter(clientSocket.getOutputStream(), true); 
      in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 

      String fromServer; 
      while ((fromServer = in.readLine()) != null) { 
       System.out.println("Server: " + fromServer); 
      } 

     } catch (IOException ex) { 
      Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 

    public static Network getInstance() { 
     return cachedInstance; 
    } 

    public void send(final String string) { 
     out.println(string); 
    } 

    @Override 
    public void run() { 
     init(); 
    } 
} 

Часть класса контроллера:

public void clientTest() { 
    int random = new Random().nextInt(1000); 
    Network.getInstance().start(); 
    Network.getInstance().send(random + ""); 
} 

Ошибка я получаю:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException 
at network.Network.send(Network.java:52) 
at controller.Controller.clientTest(Controller.java:126) 

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

Второй вопрос у меня есть, если я могу избежать использования этого:

Network.getInstance().start(); 

Другими словами, я хотел бы, чтобы гарантировать, что только один поток (Network класс) создается и что он всегда работает по умолчанию когда классы инициализируются. Это не плохо в нынешнем виде, но я просто подумал, что было бы лучше.

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

С уважением.

EDIT: Предлагаемое решение, основанное на реакциях

Network.class:

public class Network implements Runnable {  
    private static final Network cachedInstance; 
    static { 
     Network tempInstance = null; 
     try { 
      tempInstance = new Network(Config.HOST_NAME, Config.HOST_PORT); 
     } catch (IOException ex) { 
      Logger.getLogger(Network.class.getName()).log(Level.SEVERE, null, ex); 
     } finally { 
      cachedInstance = tempInstance; 
     } 
    } 

    private final Socket clientSocket; 
    private final PrintWriter out; 
    private final BufferedReader in; 

    private Network(final String hostname, final int port) throws IOException { 
     clientSocket = new Socket(hostname, port); 
     out = new PrintWriter(clientSocket.getOutputStream(), true);   
     in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 
    } 

    public static Network getInstance() { 
     return cachedInstance; 
    } 

    @Override 
    public void run() { 
     try { 
      String fromServer; 
      while ((fromServer = in.readLine()) != null) { 
       System.out.println("Server: " + fromServer); 
      } 
     } catch (IOException ex) { 
      Logger.getLogger(Network.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 

    public void send(final String string) { 
     out.println(string); 
    } 

    public void close() { 
     try { 
      in.close(); 
      out.close(); 
      clientSocket.close(); 
     } catch (IOException ex) { 
      Logger.getLogger(Network.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 
} 

телефонный код:

public void clientTest() { 
    int random = new Random().nextInt(1000); 
    Network network = Network.getInstance(); 
    new Thread(network).start(); 
    network.send(random + ""); 
    network.close(); 
} 

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

+0

Вы вызываете send перед установкой 'out'. BTW Вам нужно сделать поле 'out' полем volatile, или вы никогда не увидите, что он установлен. –

ответ

1

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

public class Network implements Runnable, Closeable {  
    private final Socket clientSocket; 
    private final PrintWriter out; 
    private final BufferedReader in; 
    private volatile boolean closed = false; 

    public Network(String hostname, int port) throws IOException {   
     clientSocket = new Socket(hostname, port); 
     out = new PrintWriter(clientSocket.getOutputStream(), true); 
     in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 
    } 

    public void run() { 
     try { 
      for(String fromServer; (fromServer = in.readLine()) != null;) 
       System.out.println("Server: " + fromServer); 
     } catch (IOException ex) { 
      if (!closed) 
       Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 

    public void send(String line) { 
     out.println(line); 
    } 

    public void close() { 
     closed = true; 
     try { clientSocket.close(); } catch (IOException ignored) { } 
    } 
} 

Для тестирования

@Test 
public void testClient() { 
    Network network = new Network(Config.HOSTNAME, Config.PORT) 
    new Thread(network).start(); 

    int random = new Random().nextInt(1000); 
    network.send(random + ""); 
    network.close(); 
} 

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

+0

Большое вам спасибо! На самом деле я уже отказался от этого вопроса. Я проверю его прямо сейчас, но он должен определенно работать. – skiwi

+0

Один вопрос: почему конструктор бросает IOException? Разве это не означает, что вызывающий код этого кода должен выполнять обработку исключений? Который, на мой взгляд, раздражает, так как сам класс (Сеть здесь) может также сделать это сам? – skiwi

+0

Он может выполнять обработку исключений, но обычно только вызывающий абонент знает, что делать. В любом случае вы не хотите создавать Сеть, которая на самом деле не связана ни с чем. т.е. мертвый объект. Что вы хотите, если сокет не может подключиться? –

2

Когда вы вызываете start() в потоке, период времени проходит до того, как поток фактически запущен. Вы вызываете send() до того, как run() вызывается, и поэтому перед инициализацией out. Не делай этого. Подождите, пока сообщение из текущего потока, и только тогда безопасно позвонить send(). Вы можете использовать wait() и notify() для простого Object для этого.

Чтобы избежать вызова клиента start() - обязательно, вызовите start() из конструктора Network.

+0

«Дождитесь сообщения из текущей темы» Не могли бы вы объяснить мне больше об этом? Как я буду ждать этого сообщения? – skiwi