Foojay Today

Java Thread Programming (Part 2)

October 12, 2021

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!

Topics:

Related Articles

View All
  • Java Thread Programming (Part 1)

    We write code in a file line by line, and then it gets executed. To be able to execute a piece of code requires an execution environment. In Java, a thread is an executing environment. If a program has only one executing environment, then we call this program a single-threaded program.

    Read More
    Oct 07, 2021
  • 🚀 Demystifying JVM Memory Management

    In this multi-part series, I aim to demystify the concepts behind memory management and take a deeper look at memory management in some of the modern programming languages, in particular Java, Kotlin, Scala, Groovy, and Clojure.

    I hope the series would give you some insights into what is happening under the hood of these languages in terms of memory management.

    In this chapter, we will look at the memory management of the Java Virtual Machine (JVM) used by languages like Java, Kotlin, Scala, Clojure, Groovy, and so on.

    Read More
    May 20, 2021
  • Avoid Multithreading Bugs Using Immutable Java 16 Records

    In a multi-threaded Java application, any thread can change the state of an object.

    The Java memory model in Java language specification specifies when exactly updates made by one thread are going to be visible to other threads.

    This is one of the biggest problems professional Java developers deal with every day.

    Java records are immutable. An object is considered immutable if its state cannot change after it is constructed. The immutable nature of records eliminates problems of its usage in a multithreaded environment.

    Read More
    Jun 07, 2021

Author(s)

  • A N M Bazlur Rahman

    Bazlur has more than ten years of experience coding in Java, including numerous academic, professional, and personal projects. He is the founder and current moderator of a Java user group ... Learn more

Comments (0)

Your email address will not be published. Required fields are marked *

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

Save my name, email, and website in this browser for the next time I comment.

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard