Java Thread Programming (Part 2)

Posted on by

Categories:         

This article was first published in Foojay.io

In our earlier article, we explained the background to threading and how to create and start a thread.

In this article, let’s see an example where we can use Threads to our benefit.

Let’s assume we are going to build a web server. For the sake of the example, let’s constrain ourselves to one single use case, which is that the web server will listen to any client, and if it receives a URL, it will return the top five most frequently used words in that website.

OK, enough talk, let’s see the code!

package com.bazlur;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.stream.Collectors;

public class SingleThreadedServer {
  private final MostFrequentWordService mostFrequentWordService = new MostFrequentWordService();

  public SingleThreadedServer(int port) throws IOException {
    var serverSocket = new ServerSocket(port);
    while (true) {
      var socket = serverSocket.accept();
      handle(socket);
    }
  }

  private void handle(Socket socket) {
    System.out.println("Client connected: " + socket);

    try (var in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
         var out = new PrintWriter(socket.getOutputStream(), true)) {
      String line;

      while ((line = in.readLine()) != null) {
        if (isValid(line)) {
          var wordCount = mostFrequentWordService.mostFrequentWord(line)
                  .stream()
                  .map(counter -> counter.word() + ": " + counter.count())
                  .collect(Collectors.joining("\n"));
          out.println(wordCount);
        } else if (line.contains("quit")) {
          out.println("Goodbye!");
          socket.close();
        } else {
          out.println("Malformed URL");
        }
      }
    } catch (IOException e) {
      System.out.println("Was unable to establish or communicate with client socket:" + e.getMessage());
    }
  }

  private static boolean isValid(String stringURL) {
    try {
      new URL(stringURL);
    } catch (MalformedURLException e) {
      System.out.println("invalid url: " + stringURL);
      return false;
    }
    return true;
  }

  public static void main(String[] args) throws IOException {
    new SingleThreadedServer(2222);
  }
}

Let’s walk through the code first. In the above code, a ServerSocket starts at a port and waits in a loop for the clients to connect. The handle() method is the most important one. It gets a Socket object and then talks to the client. If a client sends a valid URL, It calls a service, MostFrequentWordService, to get the most frequent words.

We can use telnet to connect the server and use this server.

The only problem with this is it can handle only one client at a time. So if we try to connect another client, it will respond only when the other connected client gets disconnected.

That’s certainly a problem for a web server. A web server is supposed to connect with hundreds or thousands of clients simultaneously.

We can solve this problem quite quickly, if we turn this single-threaded program into a multi-threaded program. Recall the handle() method from the above code. Whenever a client connects, I can spawn a new thread and hand over the handle() method to that Thread:

Yes, that’s the trick. Let’s do it:

public class MultiThreadedServer {
  private final MostFrequentWordService mostFrequentWordService = new MostFrequentWordService();

  public MultiThreadedServer(int port) throws IOException {
    var serverSocket = new ServerSocket(port);
    while (true) {
      var socket = serverSocket.accept();

      var thread = new Thread(() -> handle(socket));
      thread.start();
    }
  }

  //rest of the code. 
}

Now, we can connect multiple clients at once, and serve them all simultaneously:

Now that we understand the benefits of using threads in Java, we will dig a bit deeper into using threads in the following articles in this series.

And in case you are interested in how I wrote the “MostFrequentWordService”, here it is:

package com.bazlur;

import org.jsoup.Jsoup;

import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

record WordCount(String word, long count) {
}

public class MostFrequentWordService {
  public List mostFrequentWord(String url) throws IOException {
    var wordCount = Arrays.stream(getWords(url))
            .filter(value -> !value.isEmpty())
            .filter(value -> value.length() > 3)
            .map(String::toLowerCase)
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

    return wordCount.entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
            .limit(5)
            .map(entry -> new WordCount(entry.getKey(), entry.getValue()))
            .collect(Collectors.toList());
  }

  private String[] getWords(String url) throws IOException {
    var connect = Jsoup.connect(url);
    var document = connect.get();
    var content = document.body().text();

    return content.split("[^a-zA-Z]");
  }
}

That’s it for today!

Don’t Forget to Share This Post!

     

Share on:

Author: A N M Bazlur Rahman

Java Champion | Software Engineer | JUG Leader | Book Author | InfoQ & Foojay.IO Editor | Jakarta EE Ambassadors| Helping Java Developers to improve their coding & collaboration skills so that they can meet great people & collaborate

100daysofcode 100daysofjava access advance-java agile algorithm arraylist article bangla-book becoming-expert biginteger book calculator checked checked-exceptions cloning code-readability code-review coding coding-convention collection-framework compact-strings completablefuture concatenation concurrency concurrentmodificationexception concurrentskiplistmap counting countingcollections critical-section daemon-thread data-race data-structure datetime day002 deliberate-practice deserialization design-pattern developers duration execute-around executors export fibonacci file file-copy fork/join-common-pool functional future-java-developers groupby hash-function hashmap history history-of-java how-java-performs-better how-java-works http-client image import inspiration io itext-pdf java java-10 java-11 java-17 java-8 java-9 java-developers java-performance java-programming java-thread java-thread-programming java11 java16 java8 lambda-expression learning learning-and-development linkedlist list local-type-inference localdatetime map methodology microservices nio non-blockingio null-pointer-exception object-cloning optional packaging parallel pass-by-reference pass-by-value pdf performance prime-number programming project-loom race-condition readable-code record refactoring review scheduler scrum serialization serversocket simple-calculator socket software-development softwarearchitecture softwareengineering sorting source-code stack string string-pool stringbuilder swing thread threads tutorial unchecked vector virtual-thread volatile why-java zoneid