0

My goal is to run multiple objects concurrently without creating new Thread due to scalability issues. One of the usage would be running a keep-alive Socket connection.

while (true) {
  final Socket socket = serverSocket.accept();
  final Thread thread = new Thread(new SessionHandler(socket)).start();
  // this will become a problem when there are 1000 threads.
  // I am looking for alternative to mimic the `start()` of Thread without creating new Thread for each SessionHandler object.
}

For brevity, I will use Printer anology.

What I've tried:

  1. Use CompletableFuture, after checking, it use ForkJoinPool which is a thread pool.

What I think would work:

  1. Actor model. Honestly, the concept is new to me today and I am still figuring out how to run an Object method without blocking the main thread.

main/java/SlowPrinter.java

public class SlowPrinter {
  private static final Logger logger = LoggerFactory.getLogger(SlowPrinter.class);

  void print(String message) {
    try {
      Thread.sleep(100);
    } catch (InterruptedException ignored) {
    }
    logger.debug(message);
  }
}

main/java/NeverEndingPrinter.java

public class NeverEndingPrinter implements Runnable {
  private final SlowPrinter printer;

  public NeverEndingPrinter(SlowPrinter printer) {
    this.printer = printer;
  }

  @Override
  public void run() {
    while (true) {
      printer.print(Thread.currentThread().getName());
    }
  }
}

test/java/NeverEndingPrinterTest.java

  @Test
  void withThread() {
    SlowPrinter slowPrinter = new SlowPrinter();
    NeverEndingPrinter neverEndingPrinter = new NeverEndingPrinter(slowPrinter);
    Thread thread1 = new Thread(neverEndingPrinter);
    Thread thread2 = new Thread(neverEndingPrinter);
    thread1.start();
    thread2.start();

    try {
      Thread.sleep(1000);
    } catch (InterruptedException ignored) {
    }
  }

Currently, creating a new Thread is the only solution I know of. However, this became issue when there are 1000 of threads.

Jason Rich Darmawan
  • 1,607
  • 3
  • 14
  • 31
  • You're looking for a `ScheduledExecutorService`. – Louis Wasserman Aug 30 '21 at 19:33
  • For arbitrary tasks that need to run concurrently for a long time there is no good substitute to threads. However, for sockets you should look into NIO (ideally with a good library) and for more general tasks some may be expressed with async code (completable futures) that perform some work and pause when blocked, letting other code run. Good luck! – ewramner Aug 30 '21 at 19:36
  • @ewramner I will check `NIO`, honestly I avoid it because in the inside, I still use `socket.getInputStream().read()` which is blocking i/o. So I have not tried it yet. Maybe worth a try for WebSocket? – Jason Rich Darmawan Aug 30 '21 at 20:09
  • If you block on I/O you will need a thread. The approaches with thread pools below won't work well if you block for many seconds at a time. – ewramner Aug 31 '21 at 07:02

3 Answers3

2

The solution that many developers in the past have come up with is the ThreadPool. It avoids the overhead of creating many threads by reusing the same limited set of threads.

It however requires that you split up your work in small parts and you have to link the small parts step by step to execute a flow of work that you would otherwise do in a single method on a separate thread. So that's what has resulted in the CompletableFuture.

The Actor model is a more fancy modelling technique to assign the separate steps in a flow, but they will again be executed on a limited number of threads, usually just 1 or 2 per actor.

For a very nice theoretical explanation of what problems are solved this way, see https://en.wikipedia.org/wiki/Staged_event-driven_architecture

GeertPt
  • 16,398
  • 2
  • 37
  • 61
  • Could you please tell me what does `usually just 1 or 2 per actor`. E.g. in my case, does `Socket socket = serverSocket.accept();` counts as actor e.g using a custom SessionHandler that implements Runnable `SessionHandler handler = new SessionHandler(socket); Thread thread = new Thread(handler); thread.start();`? In this case, if using actor model will use 2 threads for each session, it defeat my purpose to learn actor model. – Jason Rich Darmawan Aug 30 '21 at 20:02
  • You would not use 2 threads for each session, but 2 threads per actor, independent of the number of sessions. The socket listener part can also be done asynchronous, but then you can't use blocking calls anymore. – GeertPt Aug 31 '21 at 08:42
  • If you don't mind, could you explain it with a snippet of code using the Actor model with Java SE ServerSocket? I believe it would be an interesting experiment instead of using NIO – Jason Rich Darmawan Aug 31 '21 at 13:44
  • The Actor model I had in mind was not specific for ServerSockets. If you wanted to apply it to ServerSockets, you would need NIO under the hood anyway. – GeertPt Sep 01 '21 at 08:08
1

If I look back at your original question, your problem is that you want to receive keep-alive messages from multiple sources, and don't want to use a separate thread for each source.

If you use blocking IO like while (socket.getInputStream().read() != -1) {}, you will always need a thread per connection, because that implementation will sleep the thread while waiting for data, so the thread cannot do anything else in the mean time.

Instead, you really should look into NIO. You would only need 1 selector and 1 thread where you continuously check the selector for incoming messages from any source (without blocking the thread), and use something like a HashMap to keep track of which source is still sending messages.

See also Java socket server without using threads

The NIO API is very low-level, BTW, so using a framework like Netty might be easier to get started.

GeertPt
  • 16,398
  • 2
  • 37
  • 61
  • Please correct me if I am wrong. This is my current understanding. In NIO, I would listen to a `Selector` and then get the `Socket` from a Channel then call the `Socket` OutputStream write method? – Jason Rich Darmawan Aug 31 '21 at 13:44
0

You're looking for a ScheduledExecutorService.

Create an initial ScheduledExecutorService with a fixed appropriate number of threads, e.g. Executors.newScheduledThreadPool(5) for 5 threads, and then you can schedule a recurring task with e.g. service.scheduleAtFixedRate(task, initialDelay, delayPeriod, timeUnit).

Of course, this will use threads internally, but it doesn't have the problem of thousands of threads that you're concerned about.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • I tested `newScheduledThreadPool(1)` and it can't handle 2 `NeverEndingPrinter` at the same time, or the usage I thought it was to mimic somewhat like `NeverEndingPrinter1` -> `NeverEndingPrinter2` -> `NeverEndingPrinter1`. In my real case, the WebSocket connection will always be alive so I need to `while (socket.getInputStream().read() != -1) {}` waiting incoming packet inside the `run` method of the Runnable object. – Jason Rich Darmawan Aug 30 '21 at 19:53