If you want the ArrayLists to store per-client info and access it by client ID, I suggest just using a java.util.concurrent.ConcurrentHashMap with the client ID as key, as that is very straightforward to implement.
If you really need an ArrayList, I would hide it as an implementation detail in the server code. Then you would call some server methods from your Runnables to update the ArrayList. In this way you can start with something very simple like:
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);
...
}
...
}
Then, when you profile your application, if you find contention in accesses to your list you can fine tune locking or even change your implementation to a lock-free one while minimizing impact in your code. Locking is quite fast in Java nowadays so in many cases this simple solution might be good enough.