如何实现_proper_线程安全服务器?

时间:2015-04-23 10:19:58

标签: java multithreading thread-safety

想象一个在单独的线程中启动服务器的应用程序。在稍后的某个时间点,服务器将从另一个线程接收停止命令。

我在这个实现中看到的第一个问题是整个 Server.stop()方法是同步的。官方Java文档说“从同步代码中调用其他对象的方法可能会产生[带有活性”的问题“(来源:http://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html)。

第二个问题在于我认为thread-2调用Server.stop()方法的时刻thread-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

2 个答案:

答案 0 :(得分: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.此时,只要另一个线程持有它,两个线程都不能通过第二个锁,并且两个线程都不会释放它所持有的锁,直到它超过第二个锁。这两个主题都不会取得任何进展。

在这个例子中,问题很明显,但是在一个庞大而复杂的程序中,死锁的可能性并不容易。有避免死锁的策略,检测死锁的策略,以及在某些框架中打破死锁的策略。

如果你没有在大型复杂程序中使用任何这些策略,其中线程一次锁定多个锁,那么你的程序就有发生死锁的风险。

服务器程序的一个策略是让synchronized方法为工作线程执行任务排队。 (例如,像使用Swing框架的invokeLater(Runnable r)方法一样。)然后,工作线程可以安全地调用另一个对象的synchronized方法。

答案 1 :(得分:0)

  

第二个问题在于我认为thread-2调用Server.stop()方法的时刻thread-1(服务器)可以在ServerSocket.accept()方法中。这意味着ServerSocket可以同时由两个线程访问。

在对Java API进行进一步调查后,我发现这正是ServerSocket的工作方式。

  

关闭此套接字。当前在accept()中被阻塞的任何线程都将抛出SocketException。

     

如果此套接字具有关联的通道,则该通道也将关闭。

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