多线程Java服务器:允许一个线程访问另一个线程

时间:2009-11-27 09:07:41

标签: java multithreading oop coding-style

希望代码本身解释这个问题:

class Server {

    public void main() {
        // ...
        ServerSocket serverSocket = new ServerSocket(PORT);
        while (true) {
            Socket socket = serverSocket.accept();
            Thread thread = new Thread(new Session(socket));
            thread.start();
        }
        // ..
    }

    public static synchronized Session findByUser(String user) {
        for (int i = 0; i < sessions.size(); i++) {
            Session session = sessions.get(i);
            if (session.getUserID().equals(user)) {
                return session;
            }
        }
        return null;
    }

}

class Session {
    public Session(Socket socket) {
        attach(socket);
    }

    public void attach(Socket socket) {
        // get socket's input and output streams
        // start another thread to handle messaging (if not already started)
    }

    public void run() {
        // ...
        // user logs in and if he's got another session opened, attach to it
        Session session = Server.findByUser(userId);
        if (session != null) {
            // close input and output streams
            // ...
            session.attach(socket);
            return;
        }

        // ..

    }
}

我的问题是:在Server.findByUser方法中发布会话引用是否安全,是否违反OOP样式等? 或者我应该通过一些不可变的id引用会话并封装整个事物?你还会改变其他什么吗?

String sessionId = Server.findByUser(userId);
if (sessionId != null && sessionId.length() > 0) {
    // close input and output streams
    // ...
    Server.attach(sessionId, socket);
    return;
}

托马斯:

感谢您的回答。

我同意,在现实世界中,在创建Session的新实例时使用依赖注入是个好主意,但是也可能使用接口,对(代码如下)?尽管我可能应该对其进行单元测试,但我们可以考虑不这样做。然后我只需要一个Server实例。 使用静态方法而不是单线方法会是一个巨大的OO犯罪吗?

interface Server {
    Session findByUser(String user);
}

class ServerImpl implements Server {
    public Session findByUser(String user) { }
}

class Session {
   public Session(Server server, Socket socket) { }
} 

关于attach(...)方法的好点 - 我从来没有考虑过继承Session类,这可能就是为什么我没想到在构造函数中调用public方法会有多么糟糕。但实际上我需要一些公共方法将会话连接到不同的套接字,所以可能是一对方法?

class Session {
    public Session(Socket socket) {
       attach_socket(socket);
    }

    public void attach(Socket socket) {
        attach_socket(socket);
    }

    private void attach_socket(Socket socket) {
        // ...
    }
}

允许Session的客户端呼叫attach(...)似乎不对。这可能只是服务器应该访问的那些严重的方法之一。如果没有C ++的友谊关系,我该怎么办呢?不知怎的,内心阶级浮现在我的脑海中,但我没有多想,所以这可能是一条完全错误的道路。

每次收到新连接时,我都会生成一个新线程(并创建一个与之关联的新Session实例)来处理传输。这样,当用户发送登录命令时,服务器已准备好接受新连接。一旦用户的身份得到验证,我就会检查他是否已经登录(还有另一个正在进行的会话)。如果他是,那么我从它的套接字分离onging会话,关闭该套接字,将正在进行的会话连接到当前套接字并关闭当前会话。希望这更清楚地解释实际发生了什么?也许在这里使用单词 session 有点不幸。我真正拥有的是为每个连接(和3个线程)创建的4个不同的对象:套接字处理程序,消息发送方,消息接收方和会话(如果它是一个很好的解决方案,这是一个不同的问题......)。我只是尝试简单地将源代码集中在这个问题上。

我完全同意,当您可以使用地图时迭代会话列表是没有意义的。但我担心这可能是我正在努力解决的代码中的一个较小的问题(相信我)。我应该已经提到它实际上是一些遗留系统,毫不奇怪,最近发现它有一些并发性和性能问题。我的任务是修复它......当你几乎只掌握多线程的理论知识或只用它来显示进度条时,这不是一件容易的事。

如果在此之后,相当冗长,澄清你对架构的更多见解,我会非常愿意倾听。

1 个答案:

答案 0 :(得分:1)

首先应该创建Server类OO(即非静态)并在Session类中使用dependency injection

class Server {
    public Session findByUser(String user) { }
}

class Session{
   public Session(Server server, Socket socket){}
}

public void attach(..)必须是私有的,以确保封装和正确的初始化。子类可能会破坏Session类,否则就像这样:

class BadSession extends Session{

@Override public void attach(Socket socket) {
    //this is not initialized at this point

    //now the instance is broken        
  }
}

从客户端调用附件似乎也是无效的。

将Socket附加到会话的责任应该是服务器的一部分。这是决定哪个Session获取哪个Socket的正确位置。据我所知,您正在使用Socket创建一个Session。不知何故,你发现用户已经有一个Session(与另一个Socket)。现在您将当前Session连接到此Socket。现在有旧套接字有两个会话新套接字没有会话。我认为传统的Session应该有多个套接字而不是另一种方式:

Session session = findSession(userId);
session.attach(socket);

class Session{
   List<Socket> sockets;
}

在此更改之后,线程将不会分配给Sessions,而是分配给处理程序,处理一个套接字的输入流并相应地更改Session。

对方法public static synchronized Session findByUser(String user)使用synchronized不足以确保线程安全。您必须确保查找会话(按用户)和注册会话(如果用户未知)必须原子。语义应该类似于ConcurrentMap的putIfAbsent。 (迭代会话列表无论如何都没有效率。你应该使用Map<Id, Session>。)

我希望这会有所帮助。