我正在编写一个Connection
类,该类通过Command
和CommandResult
双向发送和接收数据。但是,当多个请求通过Connection
快速发送时,有些请求无法正确通过。
这听起来像是种比赛条件,但我觉得我已经为此做好了准备:
Command
的表,其中尚未收到CommandResult
, Command
是在单个线程上接收和处理的,因此这不应该成为问题。
我已经看过足够多次的代码,以至于我认为问题必须存在于其他地方,但是我的团队非常有信心Connection
是罪魁祸首。
这个样本有点长,但是我觉得我可以做一个完整的例子,它是如此之小。我确实确保已对其进行了充分的记录。要了解的重要事项是:
AwaitWrapper
只是未来。获取资源将一直阻塞,直到它被实际填充为止。Message
仅包装请求和响应,Serializer
基本上是gson包装器,Command
和CommandResult
ICommandHandler
接受Command
并输出CommandResult
。 Command
和CommandResult
的内容对此无关紧要。Connection.java:
public class Connection {
private Socket socket;
private ICommandHandler handler;
private Serializer ser;
private Lock resultsLock;
private Lock socketWriteLock;
private Map<UUID,AwaitWrapper<CommandResult>> reservations;
public Connection(Socket socket) {
ser = new Serializer();
reservations = new TreeMap<UUID,AwaitWrapper<CommandResult>>();
handler = null;
this.socket = socket;
// Set up locks
resultsLock = new ReentrantLock();
socketWriteLock = new ReentrantLock();
}
public Connection(String host, int port) throws UnknownHostException, IOException {
socket = new Socket(host, port);
ser = new Serializer();
reservations = new TreeMap<UUID,AwaitWrapper<CommandResult>>();
handler = null;
// Set up locks
resultsLock = new ReentrantLock(true);
socketWriteLock = new ReentrantLock(true);
}
/* Sends a command on the socket, and waits for the response
*
* @param com The command to be sent
* @return The Result of the command operation.
*/
public CommandResult sendCommand(Command com) {
try {
AwaitWrapper<CommandResult> delayedResult = reserveResult(com);
write(new Message(com));
CommandResult res = delayedResult.waitOnResource();
removeReservation(com);
return res;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/* Sets handler for incoming Commands. Also starts listening to the socket
*
* @param handler The handler for incoming Commands
*/
public void setCommandHandler(ICommandHandler handler) {
if (handler == null) return;
this.handler = handler;
startListening();
}
/* Starts a thread that listens to the socket
*
* Note: don't call this until handler has been set!
*/
private void startListening() {
Thread listener = new Thread() {
@Override
public void run() {
while (receiveMessage());
handler.close();
}
};
listener.start();
}
/* Recives all messages (responses _and_ results) on a socket
*
* Note: don't call this until handler has been set!
*
* @return true if successful, false if error
*/
private boolean receiveMessage() {
InputStream in = null;
try {
in = socket.getInputStream();
Message message = (Message)ser.deserialize(in, Message.class);
if (message == null) return false;
if (message.containsCommand()) {
// Handle receiving a command
Command com = message.getCommand();
CommandResult res = handler.handle(com);
write(new Message(res));
} else if (message.containsResult()) {
// Handle receiving a result
CommandResult res = message.getResult();
fulfilReservation(res);
} else {
// Neither command or result...?
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
//--------------------------
// Thread safe IO operations
private void write(Message mes) throws IOException {
OutputStream out = socket.getOutputStream();
socketWriteLock.lock();
ser.serialize(out, mes);
socketWriteLock.unlock();
}
//----------------------------------
//Thread safe reservation operations
private AwaitWrapper<CommandResult> reserveResult(Command com) {
AwaitWrapper<CommandResult> delayedResult = new AwaitWrapper<CommandResult>();
resultsLock.lock();
reservations.put(com.getUUID(), delayedResult);
resultsLock.unlock();
return delayedResult;
}
private void fulfilReservation(CommandResult res) {
resultsLock.lock();
reservations.get(res.getUUID()).setResource(res);
resultsLock.unlock();
}
private void removeReservation(Command com) {
resultsLock.lock();
reservations.remove(com.getUUID());
resultsLock.unlock();
}
//-------------------------------------------------------------------
// A Message wraps both commands and results for easy deserialization
private class Message {
...
}
}
在监视Connection
的接收方时,对于发送的某些Command
永远不会触发处理程序。它应该由每个传入的Command
触发并处理。
我正在考虑放弃保留表并锁定对套接字的写入,直到收到响应为止,但是我希望这样做不会有明显的性能损失。
我错过了一些阻止比赛条件的关键步骤吗?
编辑:为好奇的人添加Serializer
和ICommandHandler
类。
Serializer.java:
public class Serializer {
private Gson gson;
public Serializer() {
gson = new Gson();
}
public Object deserialize(InputStream is, Class type) throws IOException {
JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8));
reader.setLenient(true);
if (reader.hasNext()) {
Object res = gson.fromJson(reader, type);
return res;
}
return null;
}
public void serialize(OutputStream os, Object obj) throws IOException {
JsonWriter writer = new JsonWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
gson.toJson(obj, obj.getClass(), writer);
writer.flush();
}
}
ICommandHandler:
public interface ICommandHandler {
public CommandResult handle(Command com);
public void close();
}
答案 0 :(得分:-1)
在您的情况下,锁不执行任何操作,那里没有竞争条件,如果同一处理程序用于多个套接字,则锁需要位于处理程序内部,您使用的是单线程客户端,因此锁可以注意:使用锁时,请使用try-finally
。
如果您只是从Sockets开始,您可能不知道这一点,但是SocketChannel比极其古老的Socket类要高效得多。
如果没有看到Serializer
和ICommandHandler
,我将为您提供更多帮助。
Serializer
中最有可能是问题。