In this article, I’m going to write an echo server in multiple ways. The server will not do anything; it will just keep listening for connections on a port and echo back whatever is sent. Then I will compare which way wins.
Let’s first write the single-threaded echo sever-
The above code is simple and intuitive. We have used a ServerSocket that listens to a port and waits for an incoming connection. Whenever a client connects to it, it reads whatever is sent to it and replies back.
The only problem is, it cannot handle multiple connections simultaneously. If one client connects to it, no other will be able to make it to the server at that particular time. Once that specific client disconnect, other will get a chance.
However, we can change the experience just by allowing multiple threads to handle each connection here. Let’s look at the code-
In the above code, we haven’t done anything extraordinary. We have just created a ThreadPool using Executors, and whenever a client connects, we put that in a thread to handle that connection.
This server will do just fine dealing with many connections simultaneously. However, we are not just happy with the word many. We want to know exactly how many connections it can handle.
The answer lies in how many threads we can spawn. It is certainly not limited by the socket. Because we know modern OS practically can handle millions of open sockets at a time. So let’s rewrite the question again- Can OS handle such an amount of Threads?
Well, the answer is no. Threads are limited and heavy. Let’s find out how much we can write a simple java program.
This little program will demonstrate how many threads we can create on a machine. We can conclude that we are limited by the number of threads we can have and that many connections we can handle simultaneously.
Now the bigger question is, is there any alternative solution to it? We cannot have more threads; what else we can do to achieve an outstanding throughput.
Well, the answer is Non-Blocking IO. While doing the IO operation, the relationality is that most threads are just waiting idle, doing nothing. It connects to a client, and the client keeps communicating with a dedicated thread. But the communication is slow, and most of the time, the thread is idle, sitting there doing nothing. Why not use this thread’s idle time to serve other clients. That seems to be a fantastic idea. Let’s do this.
The above code seems a lot complex than the earlier and a lot more lines than the previous codebase. However, with the above code, we can achieve whatever we want to achieve. This single-threaded would give us a better throughput. We can even make it even better if we throw a few more threads to it.
We achieved it, but with a complex programming model, which isn’t easy. It has many other problems; one particular is that it will become harder to debug with this complex model.
Is there any other way?
Well, that’s where we see the light at the end of the tunnel, the Project loom. It would enable just writing million virtual threads without making any effort, and the programming model would remain the same, and we will achieve the outstanding throughput we intended to. Let’s see an example-
The programming model is the same as we did earlier. We are just using a different Executor, that’s it. But this would enable us to have millions of threads.
Cheers.
for copy/paste pleasure: https://github.com/rokon12/100DaysOfJava/blob/main/src/main/java/com/bazlur/Day018_1.java