想象一个在单独的线程中启动服务器的应用程序。在稍后的某个时间点,服务器将从另一个线程接收停止命令。
我在这个实现中看到的第一个问题是整个 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
答案 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--