2015-04-23 34 views
0

想象一下在獨立線程中啓動服務器的應用程序。在稍後的某個時間點,服務器將接收來自另一個線程的停止命令。如何實現_proper_線程安全服務器?

我看到這個實現的第一個問題是整個 Server.stop()方法是同步的。官方的Java文檔說「從同步代碼中調用其他對象的方法可能會產生活性問題」(來源:http://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html)。

第二個問題是在我看來,線程2調用Server.stop()方法線程1(服務器)的時刻可能在ServerSocket.accept()方法。這意味着ServerSocket可以被兩個線程同時訪問。

這些問題實際上是否會導致問題,或者服務器代碼是否完全正常?

import java.io.IOException; 
import java.net.ServerSocket; 
import java.net.Socket; 

public class Application { 
    public static void main(String...args){  
     // Thread-1 = server thread 
     Server server = new Server(1337); 
     new Thread(server).start(); 

     // Thread-2 = any class stopping the server at some point 
     new Thread(new Runnable(){ 
      @Override 
      public void run() { 
       try { 
        Thread.sleep(1000); 
       } catch (InterruptedException ignore) { 
       } 
       // Call to questionable method 
       server.stop(); 
      }   
     }).start(); 
    } 
} 

class Server implements Runnable { 

    protected int port; 
    protected ServerSocket serverSocket; 
    protected boolean running; 

    public Server(int port) { 
     this.port = port; 
     this.serverSocket = null; 
     this.running = true; 
    } 

    public void run() { 
     try { 
      this.serverSocket = new ServerSocket(this.port); 
     } catch (IOException e) { 
      System.out.println("Server can not be started. " + e.getMessage()); 
     } 

     while (this.isRunning()) { 
      Socket socket = null; 
      try { 
       // 2. Server thread at blocking accept method 
       socket = this.serverSocket.accept(); 
      } catch (IOException e) { 
       if (!this.isRunning()) { 
        System.out.println("Server stopped."); 
        return; 
       } 
      } 
      // Do something with the socket 
      System.out.println("Client connected: " + socket.getInetAddress()); 
     } 
     System.out.println("Server terminated."); 
    } 

    private synchronized boolean isRunning() { 
     return this.running; 
    } 

    public synchronized void stop() { 
     if (running) { 
      this.running = false; 
      try { 
       // 1. Nested synchronized method call 
       this.serverSocket.close(); 
      } catch (IOException e) { 
       System.out.println("Error closing server socket."); 
      } 
     } else { 
      System.out.println("Server is already stopped."); 
     } 
    } 

} 

服務器代碼是基於:http://tutorials.jenkov.com/java-multithreaded-servers/multithreaded-server.html

回答

0

「調用其他對象從同步碼的方法可以創建問題[有]活躍」

他們警告你,當你編寫一個線程可以同時鎖定多個鎖的代碼時,如果你沒有一個計劃,那麼你就冒着死鎖的危險。

經典的死鎖情況涉及兩個線程,A和B,和兩個鎖1和2

called_in_thread_A() { 
    synchronized(lock1) { 
     synchronized(lock2) { 
      doSomething(); 
     } 
    } 
} 

called_in_thread_B() { 
    synchronized(lock2) { 
     synchronized(lock1) { 
      doSomething(); 
     } 
    } 
} 

如果這些方法被稱爲同時線程A可以獲得鎖1,而線程B同時獲得鎖定2.此時,只要另一個線程持有它,兩個線程都不能超過第二個鎖定,並且兩個線程都不會釋放它保存的鎖定,直到它超過第二個鎖定。這兩個線程都不會有任何進展。

在這個例子中,問題很明顯,但是在一個複雜的大型程序中,死鎖的可能性並不那麼容易看出來。有策略爲避免死鎖,並且策略爲檢測到死鎖,並且在一些框架中,策略爲打破死鎖。

如果您沒有在大型複雜程序中使用這些策略中的任何一個線程同時鎖定多個鎖,那麼您的程序面臨死鎖的風險。


您的服務器程序的一種策略是讓同步方法排隊等待工作線程執行的任務。 (例如,像使用Swing框架的invokeLater(Runnable r)方法一樣)。工作線程然後可以安全地調用另一個對象的​​方法。

0

第二個問題是在我看來,線程2調用Server.stop()方法線程1(服務器)的時刻可能在ServerSocket.accept()方法。這意味着ServerSocket可以被兩個線程同時訪問。

經過對Java API的進一步調查,我發現這正是ServerSocket應該如何工作的。

關閉此插座。 accept()中當前阻塞的任何線程都會拋出SocketException。

如果此套接字有關聯的通道,那麼通道也會關閉。

來源:https://docs.oracle.com/javase/8/docs/api/java/net/ServerSocket.html#close--