我正在尝试在Java中实现多线程服务器,其中服务器为连接到它的每个客户端生成一个新线程。通信在一个实现Runnable接口的类中执行,并将Socket描述符作为输入。当新客户端连接时,我使用Socket描述符编号生成一个新线程。
我需要在服务器上维护两个ArrayLists,每当新客户端加入系统时(使用客户端发送的一些数据)都会更新。如何在不同线程中运行的多个客户端之间实现共享ArrayList的这种行为?
答案 0 :(得分:1)
您可以在主服务器线程中创建并发集合的一个实例,然后通过构造函数将其传递给每个Runnable
套接字处理程序。 (听起来你已经在做这样的事情来传递Socket
本身了。)
CopyOnWriteArrayList
是并发List
实现,但效率不高。还有其他支持并发访问的集合类型,可能会提供更好的性能。
答案 1 :(得分:0)
如果您期望很多客户并希望随机访问它们,请使用ConcurrentSkipListMap。这为您提供了一个高效,线程安全,无阻塞的Map。使用索引号作为键,或使用更有意义的东西。如果你只需要一个简单的列表(没有随机访问),可以使用ConcurrentLinkedQueue,除了它是一个单链表而不是Map之外,这是完全相同的。
另一种方法是使用ArrayList(或数组),但将其视为不可变的。当您需要添加客户端时,复制旧客户端,将客户端添加到客户端,然后将旧客户端替换为旧客户端。 (CopyOnWriteArrayList,正如erickson建议的那样,为你做这件事。)这会在维护列表时花费你,但让成群的线程得到控制并一次性使用列表。不可否认,当您需要在每次添加后处理列表,可能对其进行排序,以及添加和删除受添加影响的一些条目时,此技术才变得非常有用。)
答案 2 :(得分:0)
如果您希望ArrayList
存储每个客户端信息并通过客户端ID访问它,我建议使用java.util.concurrent.ConcurrentHashMap
并将客户端ID作为密钥,因为这非常简单易于实现
如果您确实需要ArrayList
,我会将其隐藏为服务器代码中的实现细节。然后,您可以从Runnable
调用一些服务器方法来更新ArrayList
。通过这种方式,您可以从非常简单的开始:
public class Server {
...
private ArrayList<Info> infos = new ArrayList<Info>();
...
// Create one method for each *abstract* operation
// that you need to perform on the ArrayList
// (Do *NOT* implement read / write primitives
// unless that is exactly what you need. The idea is
// that all synchronization that you might need on
// the ArrayList happens inside those methods, so
// the Runnables are unaware of the implementation
// you haven chosen.)
// Here I have used example operations that are
// a bit more complex than read / write primitives
// so the intention is more clear.
public void clearInfo(int pos) {
synchronized (infos) {
infos.set(pos, null);
}
}
public Info updateInfo(int pos, Info info) {
Info oldInfo = null;
synchronized (infos) {
oldInfo = infos.get(pos);
infos.set(pos, info);
}
return oldInfo;
}
...
public Info readInfo(int pos) {
Info info = null;
synchronized (infos) {
infos.get(pos);
info.incrementReadCount();
}
return null;
}
...
}
...
public class ClientRunnable implements Runnable {
...
private Server server;
...
@Override
public void run() {
...
Info info = // getInfo();
int writePos = // getWritePos();
Info old = server.updateInfo(writePos, info);
...
int readPos = // getReadPos();
Info newInfo = server.readInfo(readPos);
...
}
...
}
然后,当您对应用程序进行概要分析时,如果您在访问列表时发现争用,则可以微调锁定甚至将实现更改为无锁定,同时最大限度地减少对代码的影响。如今,Java的锁定速度非常快,所以在很多情况下这个简单的解决方案可能已经足够好了。