Transitioning from C++ multithreading to Java multithreading

  softwareengineering

In C++, I’ve been accustomed to using threads in the following way:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m;
int i = 0; 
void makeACallFromPhoneBooth() 
{
    m.lock();
      std::cout << i << " Hello Wife" << std::endl;
      i++;
    m.unlock();//man unlocks the phone booth door 
}

int main() 
{
    std::thread man1(makeACallFromPhoneBooth);
    std::thread man2(makeACallFromPhoneBooth);

    man1.join();
    man2.join();
    return 0;
}

But when I saw tutorials on Java multithreading, the entire class seems to be handling the thread’s start and run states.

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;

   RunnableDemo( String name){
       threadName = name;
       System.out.println("Creating " +  threadName );
   }
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
     } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
     }
     System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start ()
   {
      System.out.println("Starting " +  threadName );
      if (t == null)
      {
         t = new Thread (this, threadName);
         t.start ();
      }
   }

}

public class TestThread {
   public static void main(String args[]) {

      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
   }   
}

The question:

  • Does one have to change the way one designs classes just to be able
    to make use of threading?
  • If I already have a program built without threading, and want to
    introduce threading, I could have easily done so in C++. But here, it
    seems like I’ll have to re-design the entire class or make it inherit
    from Runnable. Is this really how you work with Java threads or are
    you supposed to design it for threading right from the beginning
    instead of introducing it later?

One example of a class I’m using is this:

public class abc extends abcParent {
//many member variables and functions here

public calculateThreshold() {
    for(int i = 0; i<zillion; ++i) {
        doSomethingThatIsParallelizable();
    }
}//calculateThreshold

private doSomethingThatIsParallelizable() {
    while(something) {
        //do something else which is parallelizable
    }//while
}//doSomething

}//class

Using streams is apparently not really advisable, so how does one go about designing a class for threading? It doesn’t seem logical for abc to have start and run functions. That’s silly. abc is about abc‘s functionality alone. I’d call abcInstance.calculateThreshold(); and it’d make sense, but calling abcInstance.run() instead of calculateThreshold() doesn’t have the same readability. What would you advise?

2

These two APIs are almost exactly the same. The difference is the lack of templates and operator overloading, and the fact you need to call start() on Java’s Thread objects.

The C++ std::thread constructor is a template that takes a function. The interface for function is “()” — that can be a plain old function, or an object (such as a lambda or other std::function) which has overloaded operator().

Java’s Thread takes an object that has subclassed Runnable and implemented Run(). It’s the same pattern, except there’s no templates, meaning there needs to be a base class, and no operator overloading so the method you implement is named Run() and not operator ().

You’re right, though, that in Java (and in C++!) you will typically want to separate the logic for handling threading from the actual computation. One common approach is to have a threadpool. That lets you bound the number of parallel threads running — it doesn’t make sense to run a zillion separate threads — and keeps you from needing to pay startup/teardown costs of a single thread a zillion times.

Unlike in C++ (so far) Java’s standard library has a thread pool implementation already. java.lang.concurrent.ThreadPoolExecutor would work quite nicely here — calculateThreshold() enqueues items into its queue, where each item is a Runnable that just calls doSomethingThatIsParallelizable().

(This is a long comment converted into an answer. Its purpose is to mention the name of several paradigms, which will allow one to find more reading material.)

There are several slightly different paradigms:
(This list is not exhaustive.)

  1. Explicit threading, where a parent thread explicitly asks a child thread to be created, executed, and destroyed after its use
  2. Task parallelism, where the lines of code to be executed are packaged into a task, which will be submitted to an executor.
    • The task is an object, because it must have its own lifetime for the duration of the task.
  3. Fork-join parallelism, which allows creation of sub-tasks by a parent task that is already executing. It is a blend of the the first two.

In early days of Java, the only executor is a new thread – therefore it is basically the same mechanism as explicit threading. ExecutorService was introduced in Java 1.5 (which was more than ten years ago), alongside several implementations, one of which is based on a thread pool.


As far as software design goes, one should study the paradigms carefully, and decide which one fits the application best.
(The examples below are just introductory-level examples. Real applications have a lot more considerations and constraints.)

  • For a server-client architecture that uses a one-thread-per-connection model, explicit threading should be used, because each thread may last indefinitely as long as the connection is open. Notice that most threads would be sleeping most of the time. The maximum number of threads is not dependent on the number of CPU cores.
  • For a computationally intensive application that splits up a fixed amount of computation tasks (whose number is known at the beginning of the task) across multiple CPU cores to achieve speed-up, a thread pool having a fixed number of threads equal to the number of CPU cores would be more suitable.
  • For a computationally intensive application that splits up a recursively-defined computation task whose total amount is not yet known or is varying, fork-join parallelism is necessary.

Once you identified the best paradigm for your application, it is encouraged to refactor your application toward that paradigm. This suggestion applies to both C++ and Java (and a few other general-purpose programming languages).


For C++, you would need to find a third-party task-parallelism implementation. Intel Task Building Blocks (TBB) and Microsoft Parallel Patterns Library (PPL) are two such implementations.

It is possible to write your own C++ task-parallelism implementation. To do that, you will need to implement a lot of concurrent data structure (most importantly concurrent queues), and some imposed requirements with respect to the use of C++ exceptions.

Yeah, Java is more verbose than C++, but it’s not that verbose. Here’s how I translate your C++ code into Java8:

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;

class MyDemo {
    static final Lock m = new ReentrantLock();
    static int i = 0;
    static void makeACallFromPhoneBooth() 
    {
        m.lock();
        try {
          System.out.format("%d Hello Wifen", i);
          i++;
        } finally {
          m.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        Thread man1 = new Thread(() -> makeACallFromPhoneBooth());
        Thread man2 = new Thread(() -> makeACallFromPhoneBooth());

        man1.start();
        man2.start();

        man1.join();
        man2.join();
    }
}

The syntax, () -> makeACallFromPhoneBooth() is what passes for a lambda expression in Java8. Its value is not exactly a function (like lambda expressions in other languages) but it’s close enough: It creates a new instance of an anonymous class that implements the Runnable interface.

The compiler knows it has to be a Runnable by type inference: That’s what the Thread(r) constructor expects. And, the compiler knows to make the body of the lambda into the run() method of the anonymous class, because that’s the only method that Runnable declares.

LEAVE A COMMENT