Laboratory Training 6

Metaprogramming. Multithreading

1 Training Tasks

1.1 Individual Task

Create a graphical user interface application that is designed for plotting arbitrary functions. The user enters the real values of a and b, the functions f(x) and g(x) in the form of strings corresponding to the Java syntax. The program calculates the function h(x) according to the individual task:

Numbers of Variants
Function h(x)
Numbers of Variants
Function h(x)
1
13
af(x) – b∙g(x)
7
19
f(a + x) + b∙g(x)
2
14
f(x + a) + g(x – b)
8
20
f(a / x) – g(b∙x)
3
15
(a-f(x))(b + g(x))
9
21
f(x – a) ∙g(x + b)
4
16
f(a∙x) – g(b∙x)
10
22
f(a / x) + g(b / x)
5
17
f(x / a) ∙g(x + b)
11
23
a∙f(x) + b∙g(x)
6
18
f(a / x) – g(b / x)
12
24
af(x)g(b∙x)

After entering the necessary functions, the range of the graph display and pressing the corresponding button, the chart is plotted. You should also consider the function of clearing input lines and graphics.

To implement the processing of entered expressions, you must apply a dynamic code compilation. You should use JavaFX tools to create graphical user interface application. The recommended approach is to use the LineChart component.

1.2 Creating a GUI Application for Getting Prime Factors of Numbers

Implement JavaFX GUI application, in which the user inputs a range of numbers (from and to), and in the window the numbers and their prime factors are displayed. Realize the ability to pause, resume the stream, and also complete termination and re-calculation with new data.

1.3 Working with BlockingQueue

Create a console program in which one thread adds integers to the BlockingQueue queue, and the other calculates their arithmetic mean.

1.4 Interpretation of Mathematical Expressions

Create a console application that allows you to enter mathematical expressions, calculate and output the result. An expression can consist of constants, mathematical operations, and brackets. To implement the program, use the tools of the JavaScript language interpretation.

Note. The syntax of mathematical expressions of JavaScript is similar to Java. The result can be output using the print() function without creating additional variables.

1.5 Calculating π in a Separate Thread

Implement the program of calculation π with precision up to a given ε as the sum of a sequence:

The calculation should be done in a separate thread. When performing the calculation, you should give the user the opportunity to enter a request for the number of calculated elements of the sum.

2 Instructions

2.1 Using Metaprogramming

2.1.1 Overview. Scripting Tools

Metaprogramming is the process of creating programs that generate other programs. The most important element of metaprogramming is automatic code generation. Java offers two approaches: using scripting languages and using compilation "on the fly."

Many scripting languages and dynamically typed languages allow you to generate Java bytecodes, so that applications can run on a Java platform, just like Java programs. The javax.script package, introduced in Java SE 6, allows you to interpret expressions written in scripting languages (AppleScript, Groovy, JavaScript, Jelly, PHP, Python, Ruby, etc.).

To process scripts, you need to download the script engine. In Java 8, the engine for processing scripts, in particular, the JavaScript language (Nashorn JavaScript Engine), was a part of the JDK. In the latest versions of Java, the scripting engine must be downloaded separately. GraalVM JavaScript tools should be used instead of the Nashorn JavaScript Engine. The following dependencies should be added to the pom.xml file in the Maven project:

<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>22.0.0</version>
</dependency>
<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>22.0.0</version>
</dependency>

Now you can interpret the code written in JavaScript:

package ua.inf.iwanoff.java.advanced.sixth;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class EvalScript {

    public static void main(String[] args) throws Exception {
        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName("graal.js");
        engine.eval("print('Hello, World')");
    }

}

As can be seen from the example, the ScriptEngineManager class allows you to create a JavaScript engine, and an instance of the ScriptEngine class interprets the expressions.

Running the example will result in warnings related to the need to configure logging and poor performance due to the use of interpretation. But the script will be executed. In order to remove unnecessary warnings, instead of the standard means of working with the script engine, you can use the classes provided by GraalVM JavaScript. A result similar to the previous one, but without unnecessary warnings, can be obtained by creating an object of type org.graalvm.polyglot.Context:

package ua.inf.iwanoff.java.advanced.sixth;

import org.graalvm.polyglot.Context;

public class EvalScript {

    public static void main(String[] args) throws Exception {
        try (Context ctx = Context.newBuilder("js")
                .option("engine.WarnInterpreterOnly", "false")
                .build()) {
            ctx.eval("js", "print('Hello, World');");
        }
    }

}

An expression, or part of it, can be read from the input stream, for example, from the keyboard. The ScriptEngine class also allows you to create script language variables (put() method), call its functions (the invokeFunction() method), etc.

To transfer information from Java to the script, the binding interface Bindings is used:

import javax.script.Bindings;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class BindingDemo {

    public static void main(String[] args) throws Exception {
        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName("graal.js");
        engine.put("a", 1);
        engine.put("b", 5);

        Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
        Object a = bindings.get("a");
        Object b = bindings.get("b");
        System.out.println("a = " + a);
        System.out.println("b = " + b);

        Object result = engine.eval("c = a + b;");
        System.out.println("a + b = " + result);
    }

}

2.1.2 Dynamic Code Generation

One of Java's capabilities is to call the Java compiler in the source code. For this purpose, package javax.tools is used. The javax.tools.JavaCompiler class compiles the specified source code into a .class file. You can create an instance of JavaCompiler using the factory method getSystemJavaCompiler() of the javax.tools.ToolProvider class:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

For example, the directory c:\java contains a subdirectory (package) called test, with the file Test.java inside:

package test;

public class Test {

    public static void main(String[] args) {
        System.out.println("Hello, Dynamic Compiling!");
    }

}

By calling the run() function of the compiler object, you can compile the source code. The first three parameters of the run() function are, respectively, the input, output, and error outputs. These streams are used in the compilation process. If the values of the parameters are set to null, System.in, System.out and System.err respectively will be used. The fourth and subsequent parameters will indicate arguments and compilation options. In the simplest case, you only need to specify the full name of the file with the source code. Example:

compiler.run(null, null, null, "c:/java/test/Test.java");

Now the compiled program can be loaded for execution. In order to load a class not specified in the CLASSPATH variable, you should use a special class loader, the so-called java.net.URLClassLoader. The constructor of this class requires as argument an array of URL objects. The URL class represents a Uniform Resource Locator, a pointer to a "resource" in the World Wide Web. A resource can be something simple, such as a file or directory, or it can be a reference to a more complex object, such as a query to a database or to a search engine. In our case, this array will consist of one element. You can get such an item from the java.net.URI object (Uniform Resource Identifier), which, in turn, can be obtained from the java.io.File object:

URLClassLoader classLoader = new URLClassLoader(
    new URL[] { new File("c:/java").toURI().toURL() });

Obtained classLoader object can be used to load the class:

Class<?> cls = Class.forName("test.Test", true, classLoader);

Calling the main() method:

Method m = cls.getMethod("main", String[].class);
m.invoke(null, new Object[] { new String[] { } });

Note: as can be seen from the example, in order to pass the array parameter, it is necessary to make another array of Object type from arrays.

All the code that includes compiling and executing the compiled program would be as follows:

package ua.inf.iwanoff.java.advanced.sixth;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class CompileDemo {
    public static void main(String[] args) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        try (URLClassLoader classLoader = new URLClassLoader(
                new URL[]{new File("c:/java").toURI().toURL()})) {
            compiler.run(null, null, null, "c:/java/test/Test.java");
            Class<?> cls = Class.forName("test.Test", true, classLoader);
            Method m = cls.getMethod("main", String[].class);
            m.invoke(null, new Object[]{new String[]{}});
        }
    }
}

In a more complex case, in addition to the JavaCompiler class, you should involve other features of the javax.tools package. In particular:

  • JavaCompiler.CompilationTask – a class that represents a compilation task; compilation is carried out by invocation of call() method;
  • JavaFileManager controls the reading and writing of files when compiling;
  • JavaFileObject – a file object that allows abstraction from Java source code and class files;
  • DiagnosticListener – listener for diagnostic compilation events.

If the source code is not taken from the file system, you need to create a class that implements the JavaFileObject interface. Java provides a simple implementation of this interface (SimpleJavaFileObject class). You can create a derived class and override methods as needed.

2.2 Working with Threads. Overview

A process is a copy of a computer program that is loaded in memory is executed. In modern operating systems, a process can be executed in parallel with other processes. A process has its own address space, and this space is physically unavailable for other processes.

Most Java Virtual Machine implementations run within a single process. A Java program can create additional processes and organize interaction between them using a ProcessBuilder object. In the simplest example below, the program notepad.exe is launched:

package ua.inf.iwanoff.java.advanced.sixth;

public class StartNotepad {

    public static void main(String [] args) throws java.io.IOException {
        String[] command = {"notepad"}; // the array can also contain program options
        ProcessBuilder processBuilder = new ProcessBuilder(command);
        processBuilder.start();
    }
  
}

A thread (execution thread) is a separate subtask that can be executed in parallel with other subtasks (threads) within a single process. Each process contains at least one thread called the main thread. All threads created by the process are executed in the address space of this process and have access to process resources. Creating threads is essentially less resource-intensive operation than creating new processes. Threads are sometimes referred to as lightweight processes.

If the process has created multiple threads, then all of them are executed in parallel, while the time of the central processor (or several central processors in multiprocessor systems) is distributed among these threads. Distribution of the CPU time is performed by a special module of the operating system, the so-called scheduler. The scheduler in turn handles the management of individual thread, so even in the one-processor system an illusion of parallel running of threads is created. Time distribution is performed by interruptions of the system timer.

Threads are used to implement independent subtasks within a single process in order to create background processes, simulate the parallel execution of certain actions, or enhance the user interface's convenience.

2.3 Low-Level Thread Tools

2.3.1 Creation of Threads

There are two ways to create a thread object using the Thread class:

  • creation of a new class derived from the java.lang.Thread class and creating an object of a new class;
  • creation of a class that implements the java.lang.Runnable interface; an object of this class is passed to the java.lang.Thread class constructor as an argument.

When creating a derived class from Thread, it is necessary to override its run() method. After creating the thread object, it should be started using the start() method. This method performs some initialization and then calls run(). The following example creates a separate execution thread that outputs the next integer from 1 to 40:

package ua.inf.iwanoff.java.advanced.sixth;

public class ThreadTest extends Thread {
  
    @Override
    public void run() {
        for (int counter = 1; counter <= 40; counter++) {
            try {
                Thread.sleep(1000); // Delay time in milliseconds
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(counter);
        }
    }

    public static void main(String[] args) {
        new ThreadTest().start();
        // You can add actions that run in parallel with the run() method.
    }

}

Calling the sleep() method causes the thread to be suspended at the specified number of milliseconds. Calling the sleep() method requires catching of the InterruptedException exception that is thrown if this thread is interrupted by another thread. A similar example is with the creation of a class object that implements the Runnable interface. In this case, you also need to run the thread using the start() method:

package ua.inf.iwanoff.java.advanced.sixth;

public class RunnableTest implements Runnable {
  
    @Override
    public void run() {
        for (int counter = 1; counter <= 40; counter++) {
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(counter);
        }
    }  

    public static void main(String[] args) {
        new Thread(new RunnableTest()).start();
        // You can add actions that run in parallel with the run () method
    }

}

The second approach is better, since in this case we are free to choose the base class.

You can assign names to individual threads (the second parameter of the constructor) and get these names using the getName() function.

The setPriority() function allows you to modify priority of a thread. It is not recommended to set highest priority (Thread.MAX_PRIORITY constant).

2.3.2 Thread States

Any thread can be in several standard states. The thread receives the state "new" (Thread.State.NEW) when the thread object is created. Calling the start() method moves the thread to the state "runnable" (Thread.State.RUNNABLE). There are states "blocked" (Thread.State.BLOCKED), "blocked in time", or "in the standby mode" (Thread.State.TIMED_WAITING), "waiting", or "not working" (Thread.State.WAITING) and "complete" (Thread.State.TERMINATED). When creating a thread, it gets a "new" state and is not executed. You can call the getState() method to get the state of a thread. The following example shows some states of the thread within its life cycle:

package ua.inf.iwanoff.java.advanced.sixth;

public class StateTest {

    public static void main(String[] args) throws InterruptedException {
        Thread testThread = new Thread();
        System.out.println(testThread.getState()); // NEW
        testThread.start();
        System.out.println(testThread.getState()); // RUNNABLE
        testThread.interrupt(); // interrupt the thread
        Thread.sleep(100);      // it takes time to complete the thread
        System.out.println(testThread.getState()); // TERMINATED
    }
}

The wait(long timeout) method, like sleep(long timeout) method, allows you to pause the thread for a specified number of milliseconds. When executing this method, InterruptedException can also be thrown. The use of these methods allows you to switch the thread to standby (TIMED_WAITING). Unlike sleep(), the ability to work after calling the wait() method can be restored using notify() or notifyAll() methods.

The wait() method can be called without parameters. The thread thus gets into the "disable" state (WAITING).

A less reliable alternative way to stop a thread for a while is calling the yield() method, which should stop the thread for a certain amount of time, so that other threads can perform their actions.

After the run() method is completed, the thread finishes its work and passes to the passive state (TERMINATED).

In earlier versions of Java, for the forced stop, as well and temporary suspension of the thread and subsequent resumption, the use class Thread methods stop(), suspend() and resume() was provided. The stop(), suspend() and resume() methods are considered undesirable to use (deprecated methods), since they provoke the creation of unreliable and difficult to debug code. Furthermore, the use of suspend() and resume() can trigger deadlocks.

You can interrupt the thread that is in the standby state (Thread.State.TIMED_WAITING) by calling the interrupt() method. In this case, the InterruptedException is thrown. We can modify the previous example so that it allows us to interrupt the thread:

package ua.inf.iwanoff.java.advanced.sixth;

import java.util.Scanner;

public class InterruptTest implements Runnable {

    @Override
    public void run() {
        for (int counter = 1; counter <= 40; counter++) {
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                System.out.println("Thread interrupted.");
                break;
            }
            System.out.println(counter);
        }
    }

    @SuppressWarnings("resource")
    public static void main(String[] args) {
        Thread thread = new Thread(new InterruptTest());
        thread.start();
        System.out.println("Press Enter to interrupt");
        new Scanner(System.in).nextLine();
        thread.interrupt();
    }

}

2.3.3 Joining and Interrupting Threads

The join() method allows one thread to wait for the completion of another. Calling this method inside thread t1 for thread t2 will stop the current thread (t1) until the completion of t2, as shown in the following example:

package ua.inf.iwanoff.java.advanced.sixth;

public class JoinTest {
    static Thread t1, t2;
  
    static class FirstThread implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("The first thread started.");
                Thread.sleep(1000);
                System.out.println("The primary work of the first thread has been completed.");
                t2.join();
                System.out.println("The first thread completed.");
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class SecondThread implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("The second thread started.");
                Thread.sleep(3000);
                System.out.println("The second thread completed.");
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        t1 = new Thread(new FirstThread());
        t2 = new Thread(new SecondThread());
        t1.start();
        t2.start();
    }

}

The order of message output will be as follows:

The first thread started.
The second thread started.
The primary work of the first thread has been completed.
The second thread completed.
The first thread completed.

Like sleep(), join() responds to interrupt, therefore, it is necessary to catch InterruptedException.

You can create a thread whose work is interrupted when the thread that created it terminates. In order to create such a thread, the setDaemon() method is called with the true parameter. The setDaemon() method must be called after the thread was created, but before the start() method is called. You can check whether there is a thread by the daemon or not using isDaemon() method. If the daemon thread creates other threads, they will also receive the status of the daemon thread.

package ua.inf.iwanoff.java.advanced.sixth;

public class DaemonThread extends Thread {
    public void run() {
        try {
            if (isDaemon()) {
                System.out.println("start of the daemon thread");
                sleep(1000); 
            }
            else {
                System.out.println("start of the regular thread");
            }
        }
        catch (InterruptedException e) {
            System.err.print("Error" + e);
        }
        finally {
            if (!isDaemon())
                System.out.println("regular thread termination");
            else
                System.out.println("daemon thread termination");
        }
    }

    public static void main(String[] args) {
        DaemonThread usual = new DaemonThread();
        DaemonThread daemon = new DaemonThread();
        daemon.setDaemon(true);
        daemon.start();
        usual.start();
        System.out.println("the last line of the main() method");
    }
}

After compiling and running, it is possible that the following message output sequence will be obtained:

start of the daemon thread
the last line of the main() method
start of the regular thread
regular thread termination

The daemon thread (due to the call to the sleep(10000) method) did not have time to complete the execution of its code before the main application thread associated with the main() method was completed. The basic property of daemon threads is the ability of the main application thread to complete execution with the end of the code of the main() method, ignoring the fact that the daemon thread is still running. If you set the delay time also for the main() thread, then the daemon thread can have time to complete its execution before the main thread ends.

start of the daemon thread
start of the regular thread
regular thread termination
daemon thread termination
the last line of the main() method

2.3.4 Threads Synchronization

In the case of multiple threads, it is important to eliminate the possibility of using one resource by two threads. If the data members of a class are declared as private and access to this memory area is possible only through methods, then collisions can be avoided by declaring these methods as synchronized. At the same time, only one thread can call a synchronized method for a specific object. Such a method or code fragment is referred to as a critical section.

The concept of the monitor is used to provide access to such code. A monitor is a certain object that blocks a code during execution by a certain thread. If the synchronized keyword is located before of a function name, the object for which this method is called (this) considered as monitor. In this case, after at least one synchronized method is called, all methods with the synchronized modifier are blocked as well.

Consider an example. The Adder class allows you to add integers to some accumulator. The AdderThread class, which implements the execution thread, provides the addition of consecutive five integer values. In the main() function of the AdderTest class, we create two threads and run them:

package ua.inf.iwanoff.java.advanced.sixth;

class Adder {
    long sum = 0;

    public void add(long value) {
        this.sum += value;
        System.out.println(Thread.currentThread().getName() + " " + sum);
    }
}

class AdderThread extends Thread {
    private Adder counter = null;

    public AdderThread(String name, Adder counter) {
        super(name);
        this.counter = counter;
    }

    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                counter.add(i);
                Thread.sleep(10);
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class AdderTest {

    public static void main(String[] args) {
        Adder adder = new Adder();
        Thread threadA = new AdderThread("A", adder);
        Thread threadB = new AdderThread("B", adder);
        threadA.start();
        threadB.start();
    }

}

The program that was started several times will produce various intermediate results due to the incorrect operation of two threads with the same object. To fix the situation, the synchronized modifier should be added before the add() method:

  synchronized public void add(long value) {
      this.sum += value;
      System.out.println(Thread.currentThread().getName() + " " + sum);
  }

Sometimes declaring the entire method as synchronized is inconvenient, since in this case other threads could execute other blocking methods that are not related to this one. To solve this problem, use a block that starts with the word synchronized and contains in brackets the name of the object responsible for the lock. Any object derived from java.lang.Object (that supports the necessary methods) can act as a monitor. The following example creates a special object for blocking:

  public void add(long value) {
      Object lock = new Object();
      synchronized (lock) {
          this.sum += value;
          System.out.println(Thread.currentThread().getName() + " " + sum);
      }
  }

Note: in our case, this approach may not give the desired result; it is better to implement blocking at the level of the entire method.

If the amount of memory occupied by the object does not exceed 32 bits, writing and reading do not require synchronization. Such objects are called atomic.

When exchanging data through fields, the volatile keyword should be used before describing the corresponding field. This is due to the presence of a data caching mechanism for each thread separately, and the value from this cache may differ for each of them. Declaring a field with the volatile keyword disables such caching for it:

volatile long sum = 0;

The wait() method, called inside a synchronized block or method, stops the execution of the current thread and releases the captured object, including the lock object, from the lock. You can return blocking of a thread object by calling the notify() method for a specific thread or by calling notifyAll() for all threads. A call can only be made from another thread that blocked, in turn, the specified object.

2.4 High-Level Thread Tools

2.4.1 Disadvantages of Low-Level Multithreading Support. Overview of High-Level Tools

The use of low-level tools provides full access to the multithreading capabilities provided by modern operating systems. However, the low-level approach has a number of disadvantages:

  • the tendency to mix low-level multithreading code with code responsible for core application functionality;
  • the impossibility of restarting the thread, which necessitates the creation of redundant objects;
  • overconsumption of dynamic store due to the peculiarities of the placement of stream objects;
  • difficulties in managing shared resources;
  • problems with insufficient efficiency of synchronized code;
  • lack of effective methods of preventing mutual blocking.

The listed disadvantages are especially noticeable when creating a large number of threads.

Starting with the Java 5 version, the programmer has the opportunity to work with high-level tools for manipulating execution threads. Most of these features are available in the new java.util.concurrent package and its subpackages. The main features of the package include the following:

  • work with time units;
  • work with atomic operations;
  • flow management;
  • blocking management;
  • management of shared resources.

In addition, the Java 7 version has added tools for branching and merging threads (fork/join framework). These tools were developed specifically for the parallelization of recursive tasks. An abstract ForkJoinTask class is a "lightweight" analogue of a thread of execution. In combination with the ForkJoinPool class, this class allows you to define a large number of parallel tasks, which significantly exceeds the number of real threads.

2.4.2 Working with the TimeUnit Enumeration

The java.util.concurrent.TimeUnit enumeration is used to represent units of measurement of time intervals. It implements utilities for converting time from one unit to another. The following example demonstrates some of the time unit conversion features:

package ua.inf.iwanoff.java.advanced.sixth;

import java.util.concurrent.TimeUnit;

public class TimeTest {
    public static void main(String[] args) {
        // Get the Java VM operating time (in nanoseconds):
        long nanos = System.nanoTime();
        System.out.println(nanos);
        long micros = TimeUnit.NANOSECONDS.toMicros(nanos);
        System.out.println(micros);
        long millis = TimeUnit.MICROSECONDS.toMillis(micros);
        System.out.println(millis);
        long seconds = TimeUnit.MILLISECONDS.toSeconds(millis);
        System.out.println(seconds);
        long minutes = TimeUnit.SECONDS.toMinutes(seconds);
        System.out.println(minutes);
        long hours = TimeUnit.NANOSECONDS.toMillis(minutes);
        System.out.println(hours);
        long days = TimeUnit.HOURS.toDays(hours);
        System.out.println(days);
    }
}

There is also a universal method convert() with two parameters: duration and unit of measurement of time, which allows you to perform similar actions.

This enumeration is used primarily in the context of multithreading. The enumeration, in particular, defines methods timedWait(), timedJoin() and sleep(). For example,

TimeUnit.SECONDS.sleep(2);

provides "falling asleep" for 2 seconds. As with Thread.sleep(), the InterruptedException exception catching is required.

2.4.3 Working with Atomic Operations

In order to avoid blocking, the java.util.concurrent.atomic package provides so-called atomic operations. They are represented by classes AtomicBoolean, AtomicInteger, AtomicLong and AtomicReference. A number of methods have been implemented for classes representing atomic operations on integers, the most used of which are set(), getAndSet(), compareAndSet(), incrementAndGet(), decrementAndGet(), incrementAndGet(), and decrementAndGet(). Corresponding operations on variables are performed with security, but without applying locks. In the following example, work is carried out with AtomicLong:

package ua.inf.iwanoff.java.advanced.sixth;

import java.util.concurrent.atomic.AtomicLong;

public class AtomicLongDemo {
    public static void main(String[] args) {
        AtomicLong result = new AtomicLong(1);
        for (AtomicLong index = new AtomicLong(1); index.get() <= 20;
                                          index.incrementAndGet()) {
            result.set(result.get() * index.get());
        }
        System.out.println(result);
    }
}

As can be seen from the example, the factorial of 20 is calculated.

2.4.4 Management of Thread Creation. Thread Pools

When building large applications, it's a good idea to separate the creation and management of threads from other application code. For this purpose, the Executor, ExecutorService and ScheduledExecutorService interfaces are defined, which declare the possibility of implementing the creation of queue pools. A generic Callable interface represents some function and is similar to Runnable, but allows you to return some result and generate exceptions. The generic Future interface is used to represent the result of an asynchronous submit() operation defined in ExecutorService.

In order to take advantage of the possibilities of the thread pool, you should create an object of the ExecutorService class. The best option for creation is to use one of the factory methods of the Executors class.

The following example creates three execution threads that output the following messages:

// The parameter is the number of threads in the pool:
ExecutorService service = Executors.newFixedThreadPool(3); 
service.execute(() -> System.out.println("First"));
service.execute(() -> System.out.println("Second"));
service.execute(() -> System.out.println("Third"));

To correctly stop threads, you should use the shutdown() method:

service.shutdown();

Running the code may result in receiving messages in a different sequence.

2.4.5 High-Level Lock Tools

One of the main tasks of high-level multithreading tools is to manage blocking, which ensures the exclusion of a situation related to mutual blocking.

The java.util.concurrent.locks.Lock interface supports high-level locking controls. A java.util.concurrent.locks.ReentrantLock class that implements this interface supports limited lock release waits, aborted lock attempts, lock queues, and setting multiple lock release waits. Using blocking objects instead of low-level means, in particular, prevents deadlocks.

In particular, this is how you can set a block (prevent the execution of a code fragment for other threads) using the java.util.concurrent.locks.ReentrantLock class object (alternative to synchronized). In the example below, only one of the customers is guaranteed to withdraw funds from the account.

private Lock accountLock = new ReentrantLock();
  
public void withdrawCash(int accountID, int amount) {
    // This could be thread-safe code, 
    // like reading from a file or database

    accountLock.lock(); // block when thread comes here
    try {
        if (allowTransaction) { // if transaction is allowed
            updateBalance(accountID, amount, "Withdrawal"); // balance change
        }
        else {
            System.out.println("Insufficient funds");
        }
    }
    finally {
        accountLock.unlock(); // allow other threads to change balance
    }
}

The tryLock() method attempts to lock the lock instance immediately. This method returns true if the lock was successful and false if the instance is already locked. You can use the lock delay time and unit:

Lock lock = ...;
if (lock.tryLock(50L, TimeUnit.MILLISECONDS)) ...

2.4.6 Use of Synchronizers

Synchronizers are a set of helper classes for thread synchronization. The following synchronizers are most often used:

  • Semaphore is used to limit the number of threads when working with certain resources. Access to the common resource is controlled using a counter. If the counter is greater than zero, the flow is allowed and the counter is decremented. If the counter is zero, the current thread is blocked until another thread releases the resource.
  • CountDownLatch allows one or more threads to wait until a certain number of operations performed by other threads have completed. The number of allowable operations is specified in the class constructor. Corresponding operations should ensure that the counter is decreased by calling the countDown() method. When the counter reaches 0, all waiting threads are unblocked and continue execution.
  • CyclicBarrier is a synchronization point at which a given number of parallel threads meet and block. Once all threads have arrived, some action is performed (or not performed if the barrier was initialized without it), and after it is performed, the waiting threads are released.
  • Phaser similar to the cyclic barrier, but is more flexible and provides greater functionality.
  • Exchanger allows the exchange of data between two threads at a certain point of operation. The exchanger is a generic class parameterized by the type of the object to be transferred. The thread calling the exchange() exchanger method blocks and waits for another thread. When another thread calls the same method, objects will be exchanged.

2.5 Use Thread Execution in JavaFX Applications

The work of graphical user interface applications is usually associated with threads. After starting the JavaFX application, the application thread is automatically created to handle the events. Only this thread can interact with windows and visual components. Attempting two threads simultaneously to control the visualization of components and information on the window's surface can lead to chaotic appearance and unpredictable behavior of the visual components. However, thanks to the multithreading, you can provide better controllability of the application. In addition, it is advisable to take in a separate thread some actions that are performed for a long time. This will provide an opportunity to monitor the process, pause and continue it, change settings, etc.

JavaFX tools allow you to execute code in the thread, which is responsible for receiving and processing events. The javafx.application.Platform.runLater() method receives an object that implements the Runnable functional interface. This object enters the event processing thread, where the object becomes in the queue and, when the opportunity arises, its run() method is executed. In this method, it is advisable to implement interaction with visual components. Example 3.3 demonstrates the possibility of calling this method from a child thread.

There are also high-level mechanisms for working with threads in JavaFX, presented in the javafx.concurrent package: the abstract Task class, derived from java.util.concurrent.FutureTask, in turn provides for the creation of a derived class that implements the call() method. This method implements interaction with visual components. In addition, this class provides the ability to directly bind some components (for example, ProgressBar) to the task.

2.6 Use of Thread-Safe Collections

2.6.1 Use of Synchronized Analogues of Standard Collections

The java.util.Collections class can be used to obtain thread-safe clones of existing collections. These are static functions: synchronizedCollection(), synchronizedList(), synchronizedMap(), synchronizedSet(), synchronizedSortedMap(), and synchronizedSortedSet(). Each of these functions takes as a parameter the corresponding non-synchronized collection and returns a collection of the same type, all methods of which (except for iterators) are defined as synchronized. Accordingly, all operations, including reading, use a lock that reduces performance and restricts the use of the appropriate collections.

Iterators of standard collections implemented in java.util implement a so-called fail-fast behavior: if some object was changed bypassing the collection iterator throws the ConcurrentModificationException exception, and in terms of multithreading, the behavior of the iterator may be unpredictable: an exception may not be thrown immediately or not thrown at all. Reliable and work with collection elements are provided by special container classes defined in java.util.concurrent. These are types such as CopyOnWriteArrayList, CopyOnWriteArraySet, ConcurrentMap (interface), ConcurrentHashMap (implementation), ConcurrentNavigableMap (interface), and ConcurrentSkipListMap (implementation).

The CopyOnWriteArrayList class that implements the List interface, creates a copy of the array for all operations that change the value of items. The iterator also works with its copy. Such behavior of the iterator is called fail-safe behavior.

The ConcurrentHashMap and ConcurrentSkipListMap classes provide associative arrays that do not require blocking of all operations. The ConcurrentHashMap class organizes a data structure divided into segments. Blocking is done at the level of one segment. Due to the improved hash function, the probability of applying two threads to one segment is significantly reduced. The ConcurrentSkipListMap class implements storage using a special data structure, the skip list. The elements of the associative array are sorted by the keys. Example 3.2 demonstrates work with ConcurrentSkipListMap.

2.6.2 Work with Special Collections that Use Locks

The java.util.concurrent package provides a collection of multithreading security collections. These are generic types such as BlockingQueue (interface) and its standard implementations (ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue, DelayQueue), as well as derived from it TransferQueue interface (LinkedTransferQueue implementation) and LinkedBlockingDeque interface (standard implementation is LinkedBlockingDeque).

The BlockingQueue interface represents a queue that allows you to add and get items safely from the perspective of multithreading. A typical use of the BlockingQueue is adding objects in one thread, and getting objects in another thread.

The thread that adds objects (first thread) continues to add them to the queue until some upper limit of the permissible number of elements is reached. In this case, when trying to add new objects, the first thread will be blocked until the thread that receives the elements (second thread) does not remove at least one item from the queue. The second thread can remove and use objects from the queue. If, however, the second thread tries to get an empty queue object, this thread will be blocked until the first thread adds a new object.

The BlockingQueue interface inherits java.util.Queue, so it supports add(), remove(), element() (with exception throwing), plus offer(), poll() and peek() (with special returning value). In addition, blocking methods are put() (for adding) and take() (for extraction) are declared. There are also methods that use time-locking: offer() and poll() with additional parameters (time interval and time unit).

You cannot add null to BlockingQueue. This attempt leads to the throw of NullPointerException.

The most popular implementation of BlockingQueue is ArrayBlockingQueue. In order to store references to objects in this implementation, a conventional array of limited length is used. This length cannot be changed after creating a queue. All class constructors receive this array length as the first parameter. In the example below, two threads are created - Producer and Consumer. The producer adds integers with a delay between additions, and the consumer withdraws them

package ua.inf.iwanoff.java.advanced.sixth;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class Producer implements Runnable {
    private BlockingQueue<Integer> queue;
    int countToAdd;

    public Producer(BlockingQueue<Integer> queue, int countToAdd) {
        this.queue = queue;
        this.countToAdd = countToAdd;
    }

    public void run() {
        // We try to add numbers:
        try {
            for (int i = 1; i <= countToAdd; i++) {
                queue.put(i);
                System.out.printf("Added: %d%n", i);
                Thread.sleep(100);
            }
        }
        catch (InterruptedException e) {
            System.out.println("Producer interrupted");
        }
    }
}

class Consumer implements Runnable {
    private BlockingQueue<Integer> queue;
    int countToTake;

    public Consumer(BlockingQueue<Integer> queue, int countToTake) {
        this.queue = queue;
        this.countToTake = countToTake;
    }

    public void run() {
        // We withdraw numbers:
        try {
            for (int i = 1; i <= countToTake; i++) {
                System.out.printf("Taken by customer: %d%n", queue.take());
            }
        }
        catch (InterruptedException e) {
            System.out.println("Consumer interrupted");
        }
    }
}

public class BlockingQueueDemo {

    public static void main(String[] args) throws InterruptedException {
        // Create a queue of 10 items:
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
    
        // Create and launch two threads - for writing and reading:
        Thread producerThread = new Thread(new Producer(queue, 100));
        Thread consumerThread = new Thread(new Consumer(queue, 4));
        producerThread.start();
        consumerThread.start();
    
        // Wait 10 seconds and interrupt the first thread:
        Thread.sleep(10000);
        producerThread.interrupt();
    }
  
}

When you start the program, you can see that the producer thread consistently adds numbers to the queue. The first four numbers are withdrawn by the consumer thread, while the consumer thread is blocked each time, waiting for the next number. Since only 10 numbers can fit in the queue, the queue quickly becomes full after the consumer thread stops withdrawing items. After ten seconds, the main thread interrupts the producer thread.

The implementation of LinkedBlockingQueue differs from ArrayBlockingQueue in its internal representation: a linked list is used. The maximum size is no longer significant (Integer.MAX_VALUE by default), but it can also be specified in the constructor.

The PriorityBlockingQueue class uses the same ordering rules as java.util.PriorityQueue.

The SynchronousQueue class represents a queue that can contain only one element. The thread that added the item is blocked until another thread receives it from the queue. If the second thread cannot get an element (the queue is empty), this thread is also blocked until a new element is added by the first thread.

The implementation of DelayQueue allows you to remove only those elements that have been expired for some time (delay). The elements of this queue should implement the interface java.util.concurrent.Delayed. This interface defines the getDelay() method, which returns the delay remaining for the object. From the queue, the element for which the delay time has passed before the other is removed in the first place. If none of the elements is delayed, the poll() method returns null.

The BlockingDeque interface, derived from the BlockingQueue, additionally supports the aaddFirst(), addLast(), takeFirst(), and takeLast() operations, which are inherent in double-ended queues. The LinkedBlockingDeque class offers a standard implementation of this interface.

2.7 Standard Tools for Creating Graphs and Diagrams

The standard JavaFX tools provide ready-made components for plotting and charting. The Abstract Class Chart is a base for all charts and diagrams and combines the properties of all components intended to visualize numeric data. Directly derived from it are the PieChart and XYChart classes. The XYChart class is also abstract and serves as the base class for all other charts: AreaChart, BarChart, BubbleChart, LineChart, ScatterChart, StackedAreaChart, and StackedBarChart.

The constructors of classes derived from XYChart require the definition of two objects of the Axis type. Axis is an abstract class for representing an axis on a chart or diagram. The objects of derived CategoryAxis class are used when some strings are allocated along the axis. Another derived type is an abstract ValueAxis class. The NumberAxis class derived from ValueAxis is used when the axis represents a scale of numeric values.

The following example demonstrates the possibilities of constructing some diagrams:

package ua.inf.iwanoff.java.advanced.sixth;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class ChartDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Chart Demo");

        PieChart pieChart = new PieChart();
        pieChart.setMinWidth(300);
        pieChart.setTitle("Pie Chart");
        pieChart.getData().addAll(new PieChart.Data("two", 2),
                                  new PieChart.Data("three", 3),
                                  new PieChart.Data("one", 1));

        XYChart.Data<String, Number>[] barData = new XYChart.Data[] {
                                  new XYChart.Data<>("one", 2.0),
                                  new XYChart.Data<>("two", 3.0),
                                  new XYChart.Data<>("three", 1.0) };
        BarChart<String, Number> barChart = new BarChart<>(new CategoryAxis(), new NumberAxis());
        barChart.setTitle("Bar Chart");
        barChart.getData().addAll(new Series<>("Bars", FXCollections.observableArrayList(barData)));

        XYChart.Data<Number, Number>[] xyData = new XYChart.Data[] {
                new XYChart.Data<>(1.0, 2.0),
                new XYChart.Data<>(2.0, 3.0),
                new XYChart.Data<>(3.0, 1.0) };
        AreaChart<Number, Number> areaChart = new AreaChart<>(new NumberAxis(), new NumberAxis());
        areaChart.setTitle("Area Chart");
        areaChart.getData().addAll(new Series<>("Points", FXCollections.observableArrayList(xyData)));
        // Uses the same data set
        BubbleChart<Number, Number> bubbleChart = new BubbleChart<>(
                new NumberAxis(0, 4, 1), new NumberAxis(0, 5, 1));
        bubbleChart.setTitle("Bubble Chart");
        bubbleChart.getData().addAll(new Series<>("Bubbles", FXCollections.observableArrayList(xyData)));

        HBox hBox = new HBox();
        hBox.getChildren().addAll(pieChart, barChart, areaChart, bubbleChart);
        hBox.setSpacing(10);
        hBox.setPadding(new Insets(10, 10, 10, 10));

        Scene scene = new Scene(hBox,  1000, 350);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
      

The main window of the application will look like this:

For charts derived from XYChart, you can define multiple data sets (Series) to visually compare different dependencies.

Among the classes derived from XYChart, the LineChart class, whose objects can be used to construct function graphs, is of particular interest. To construct a graphic, it is first necessary to prepare the dependence data in the form of a set of points. The amount of points should be large enough to make the curve smooth, but not too much, so as not to slow down the program's execution. The above program builds two trigonometric functions on one graph:

package ua.inf.iwanoff.java.advanced.sixth;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;

import java.util.function.DoubleUnaryOperator;

public class LineChartDemo extends Application {
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Trigonometric Functions");
        Scene scene = new Scene(fgGraph(-4, 4, 1, -1.5, 1.5, 1,
                "Sine", Math::sin, "Cosine", Math::cos), 800, 350);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private LineChart<Number, Number> fgGraph(double xFrom, double xTo, double xStep,
                                              double yFrom, double yTo, double yStep,
                                              String fName, DoubleUnaryOperator f,
                                              String gName, DoubleUnaryOperator g) {
        NumberAxis xAxis = new NumberAxis(xFrom, xTo, xStep);
        NumberAxis yAxis = new NumberAxis(yFrom, yTo, yStep);
        LineChart<Number, Number> graphChart = new LineChart<>(xAxis, yAxis);
        // Turn off the "symbols" in the points
        graphChart.setCreateSymbols(false);
        double h = (xTo - xFrom) / 100;
        // Add the name and points of the first function:
        XYChart.Series<Number, Number> fSeries = new XYChart.Series<>();
        fSeries.setName(fName);
        for (double x = xFrom; x <= xTo; x += h) {
            fSeries.getData().add(new XYChart.Data<>(x, f.applyAsDouble(x)));
        }
        // Add the name and points of the second function:
        XYChart.Series<Number, Number> gSeries = new XYChart.Series<>();
        gSeries.setName(gName);
        for (double x = xFrom; x <= xTo; x += h) {
            gSeries.getData().add(new XYChart.Data<>(x, g.applyAsDouble(x)));
        }
        // Add both functions
        graphChart.getData().addAll(fSeries, gSeries);
        return graphChart;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

3 Sample Programs

3.1 Calculating the Value of an Expression with a String

When executing the next program, the user enters an expression that uses some string, and receives the result of this expression on the screen. The methods of the String class can be used. Depending on the purpose of the program, we can propose two variants of its implementation.

First Version

The first approach implies that the generation of the code will be carried out once. In the Eclipse environment, we create the StringProcessor project, the ua.inf.iwanoff.java.advanced.sixth package, and the StringProcessor class. The code can be as follows:

package ua.inf.iwanoff.java.advanced.sixth;

import java.io.*;
import java.lang.reflect.Method;
import java.util.Scanner;
import javax.tools.*;

public class StringProcessor {
    final String sourceFile = "target/classes/ua/inf/iwanoff/java/advanced/sixth/StrFun.java";

    void genSource(String expression) {
        try (PrintWriter out = new PrintWriter(sourceFile)) {
            out.println("package ua.inf.iwanoff.java.advanced.sixth;");
            out.println("public class StrFun {");
            out.println("    public static String transform(String s) {");
            // Add an empty string to convert the result of any type to a string:
            out.println("        return " + expression + "\"\";");
            out.println("    }");
            out.println("}");
        }
        catch (IOException e) {
            e.printStackTrace();
        }  
    }

    boolean compile() {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        return compiler.run(null, null, null, sourceFile) == 0;
    }

    public static void main(String[] args) {
        StringProcessor sp = new StringProcessor();
        Scanner scan = new Scanner(System.in);
        System.out.println("Enter the expression that you want to execute over the s string:");
        String expression = scan.nextLine().replaceAll("\"", "\\\"");
        sp.genSource(expression);
        try {
            if (sp.compile()) {
                System.out.println("Enter s:");
                String s = scan.nextLine();
                Class<?> cls = Class.forName("ua.inf.iwanoff.java.advanced.sixth.StrFun");
                Method m = cls.getMethod("transform", String.class);
                System.out.println(m.invoke(null, new Object[] { s }));
            }
            else {
                System.out.println("Error entering the expression!");
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Second Version

A more complex case assumes repeated input of the expression and, accordingly, multiple code generation. The problem is that after loading the class, it needs to be unloaded in order to load a new version with the same name. This is a non-trivial task that involves creating your own class loader. A simpler approach is to create classes with different names. For example, you can add successive integers to class names. Now the program will be as follows:

package ua.inf.iwanoff.java.advanced.sixth;

import java.io.*;
import java.lang.reflect.Method;
import java.util.Scanner;
import javax.tools.*;

public class StringProcessor {
    final String sourceFile = "target/classes/ua/inf/iwanoff/java/advanced/sixth/StrFun";

    void genSource(String expression, int number) {
        try (PrintWriter out = new PrintWriter(sourceFile + number + ".java")) {
            out.println("package ua.inf.iwanoff.java.advanced.sixth;");
            out.println("public class StrFun" + number + "{");
            out.println("    public static String transform(String s) {");
            out.println("        return " + expression + " + \"\";");
            out.println("    }");
            out.println("}");
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    boolean compile(int number) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        return compiler.run(null, null, null, sourceFile + number + ".java") == 0;
    }

    public static void main(String[] args) {
        int number = 0;
        StringProcessor sp = new StringProcessor();
        Scanner scan = new Scanner(System.in);
        String expression;
        do {
            System.out.println("Enter the expression that you want to execute over the s string "
                    + "(empty string to finish):");
            expression = scan.nextLine().replaceAll("\"", "\\\"");
            if (expression.isEmpty()) {
                break;
            }
            sp.genSource(expression, ++number);
            try {
                if (sp.compile(number)) {
                    System.out.println("Enter s:");
                    String s = scan.nextLine();
                    Class<?> cls = Class.forName("ua.inf.iwanoff.java.advanced.sixth.StrFun" + number);
                    Method m = cls.getMethod("transform", String.class);
                    System.out.println(m.invoke(null, new Object[]{s}));
                }
                else {
                    System.out.println("Error entering the expression!");
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        while (!expression.isEmpty());
    }

}

3.2 Working with the ConcurrentSkipListMap Container

In the following program, one thread fills ConcurrentSkipListMap with a number / list of simple factors pairs, and another thread finds prime numbers (numbers with the only simple factor) and lists them. Numbers are checked in a given range.

The PrimeFactorization class (getting simple factors):

package ua.inf.iwanoff.java.advanced.sixth;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;

public class PrimeFactorization implements Runnable {
    static volatile Map<Long, List<Long>> table = new ConcurrentSkipListMap<>();
    private long from, to;

    public long getFrom() {
        return from;
    }

    public long getTo() {
        return to;
    }

    public void setRange(long from, long to) {
        this.from = from;
        this.to = to;
    }

    // Getting a list of dividers of a given number:
    private List<Long> factors(long n) {
        String number = n + "\t";
        List<Long> result = new ArrayList<>();
        for (long k = 2; k <= n; k++) {
            while (n % k == 0) {
                result.add(k);
                n /= k;
            }
        }
        System.out.println(number + result);
        return result;
    }

    @Override
    public void run() {
        try {
            for (long n = from; n <= to; n++) {
                table.put(n, factors(n));
                Thread.sleep(1);
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        String result = table.entrySet().size() + " numbers\n";
        for (Map.Entry<?, ?> e : table.entrySet()) {
            result += e + "\n";
        }
        return result;
    }

}

The Primes class (prime numbers):

package ua.inf.iwanoff.java.advanced.sixth;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class Primes implements Runnable {
    PrimeFactorization pf;
    Set<Long> result;

    public Primes(PrimeFactorization pf) {
        this.pf = pf;
    }

    @Override
    public void run() {
        result = new LinkedHashSet<>();
        try {
            for (long last = pf.getFrom(); last <= pf.getTo(); last++) {
                List<Long> factors;
                do { // trying to get the next set of numbers:
                    factors = pf.table.get(last);
                    Thread.sleep(1);
                }
                while (factors == null);
                // found a prime number:
                if (factors.size() == 1) {
                    result.add(last);
                    System.out.println(this);
                }
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return " Prime numbers: " + result;
    }
}
      

The PrimesTest class:

package ua.inf.iwanoff.java.advanced.sixth;

public class PrimesTest {
    public static void main(String[] args) throws InterruptedException {
        PrimeFactorization pf = new PrimeFactorization();
        Primes primes = new Primes(pf);
        pf.setRange(100000L, 100100L);
        new Thread(pf).start();
        Thread primesThread = new Thread(primes);
        primesThread.start();
        primesThread.join();
        System.out.println(pf);
        System.out.println(primes);
    }
}
      

3.3 Creating a GUI Application for Calculating and Displaying Prime Numbers

Suppose it is necessary to develop a program for obtaining primes in the range from 1 to a certain value, which can be quite large. To find primes we will use the simplest algorithm for sequential checking of all numbers from 2 to the square root of the verifiable number. Such a check may take a long time. To create a user-friendly interface, it's advisable to use a separate execution thread for checking numbers. This will allow user to pause and resume searches, perform standard window manipulations, including closing window before the search will be finished.

The root pane of our application is BorderPane. The source code of the JavaFX application will be as follows:

package ua.inf.iwanoff.java.advanced.sixth;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.io.IOException;

public class PrimeApp extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        try {
            BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("PrimeAppWindow.fxml"));
            Scene scene = new Scene(root, 700, 500);
            primaryStage.setScene(scene);
            primaryStage.setTitle("Prime Numbers");
            primaryStage.show();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The user interface will include the top panel (HBox), on which will be successively allocated a label with the text "To:", a textFieldTo input field, and four buttons: buttonStart, buttonSuspend, buttonResume and buttonStop with the text "Start", "Suspend", "Resume" and "Stop". After starting a program, only the buttonStart button is available. The middle part will be occupied by the textAreaResults input area, for which editing is disabled. The lower part will be occupied by the progressBar component, in which the percentage of the search process for primes will be displayed. The FXML document file will look like this:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>

<?import javafx.scene.control.ProgressBar?>
<BorderPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.121"
            xmlns:fx="http://javafx.com/fxml/1" fx:controller="ua.inf.iwanoff.java.advanced.sixth.PrimeAppController">
    <top>
        <HBox prefHeight="3.0" prefWidth="600.0" spacing="10.0" BorderPane.alignment="CENTER">
            <padding>
                <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
            </padding>
            <Label text="To:"/>
            <TextField fx:id="textFieldTo" text="1000"/>
            <Button fx:id="buttonStart" minWidth="80.0" text="Start" onAction="#startClick"/>
            <Button fx:id="buttonSuspend" minWidth="80.0" disable="true" text="Suspend" onAction="#suspendClick"/>
            <Button fx:id="buttonResume" minWidth="80.0" disable="true" text="Resume" onAction="#resumeClick"/>
            <Button fx:id="buttonStop" minWidth="80.0" disable="true" text="Stop" onAction="#stopClick"/>
        </HBox>
    </top>
    <center>
        <TextArea fx:id="textAreaResults" editable="false" wrapText="true" BorderPane.alignment="CENTER"/>
    </center>
    <bottom>
        <ProgressBar fx:id="progressBar" maxWidth="Infinity" progress="0"/>
    </bottom>
</BorderPane>

We can generate an empty controller class, add fields associated with visual components and methods of handling events:

package ua.inf.iwanoff.java.advanced.sixth;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;

public class PrimeAppController {
    @FXML private TextField textFieldTo;
    @FXML private Button buttonStart;
    @FXML private Button buttonSuspend;
    @FXML private Button buttonResume;
    @FXML private Button buttonStop;
    @FXML private ProgressBar progressBar;
    @FXML private TextArea textAreaResults;

    @FXML
    private void startClick(ActionEvent actionEvent) {
        
    }

    @FXML
    private void suspendClick(ActionEvent actionEvent) {
        
    }

    @FXML
    private void resumeClick(ActionEvent actionEvent) {
        
    }

    @FXML
    private void stopClick(ActionEvent actionEvent) {
        
    }

}

After starting the program an application window will be the following:

Now, separately from the GUIs, you can create a class that will be responsible for calculating primes in a separate thread. The PrimeNumbers class will implement the Runnable interface. The main functions (start(), suspend(), resume() and stop()), which will be called from the controller, change the state of the object (suspended and stopped fields). To provide flexibility, the program updates data about simple numbers and percentage of process execution through the callback mechanism. The fields of type Runnable store appropriate objects. To launch run() methods in another thread, these functions are called using Platform.runLater() method. The PrimeNumbers class cheat code will look like this:

package ua.inf.iwanoff.java.advanced.sixth;

import javafx.application.Platform;

public class PrimeNumbers implements Runnable {
    private Thread primesThread;     // the thread of the calculation of primes
    private int to;                  // the end of the range of the calculation of primes
    private int lastFound;           // last found prime
    private Runnable displayFunc;    // the function that is called to output the found number
    private Runnable percentageFunc; // a function that updates the percentage of the completed process
    private Runnable finishFunc;     // a function that is called after the finishing
    private double percentage;
    private boolean suspended;
    private boolean stopped;

    public PrimeNumbers(Runnable addFunc, Runnable percentageFunc, Runnable finishFunc) {
        this.displayFunc = addFunc;
        this.percentageFunc = percentageFunc;
        this.finishFunc = finishFunc;
    }

    public int getTo() {
        return to;
    }

    public void setTo(int to) {
        this.to = to;
    }

    public synchronized int getLastFound() {
        return lastFound;
    }

    private synchronized void setLastFound(int lastFound) {
        this.lastFound = lastFound;
    }

    public synchronized double getPercentage() {
        return percentage;
    }

    private synchronized void setPercentage(double percentage) {
        this.percentage = percentage;
    }

    public synchronized boolean isSuspended() {
        return suspended;
    }

    private synchronized void setSuspended(boolean suspended) {
        this.suspended = suspended;
    }

    public synchronized boolean isStopped() {
        return stopped;
    }

    private synchronized void setStopped(boolean stopped) {
        this.stopped = stopped;
    }

    @Override
    public void run() {
        for (int n = 2; n <= to; n++)
        {
            try {
                setPercentage(n * 1.0 / to);
                // Update the percentage:
                if (percentageFunc != null) {
                    Platform.runLater(percentageFunc);
                }
                boolean prime = true;
                for (int i = 2; i * i <= n; i++) {
                    if (n % i == 0) {
                        prime = false;
                        break;
                    }
                }
                Thread.sleep(20);
                if (prime) {
                    setLastFound(n);
                    // Display prime number found:
                    if (displayFunc != null) {
                        displayFunc.run();
                    }
                }
            }
            catch (InterruptedException e) {
                // Depending on the state of the object, 
                // wait for the continuation or complete the search:
                while (isSuspended()) {
                    try {
                        Thread.sleep(100);
                    }
                    catch (InterruptedException e1) {
                        // Interrupted by waiting:
                        if (isStopped()) {
                            break;
                        }
                    }
                }
                if (isStopped()) {
                    break;
                }
            }
        }
        if (finishFunc != null) {
            Platform.runLater(finishFunc);
        }
    }

    public void start() {
        primesThread = new Thread(this);
        setSuspended(false);
        setStopped(false);
        primesThread.start();
    }

    public void suspend() {
        setSuspended(true);
        primesThread.interrupt();
    }

    public void resume() {
        setSuspended(false);
        primesThread.interrupt();
    }

    public void stop() {
        setStopped(true);
        primesThread.interrupt();
    }
}

As you can see from the code, data access methods are implemented with the synchronized modifier, which prevents simultaneous writing and reading of partially completed data from different threads. Therefore, all references to class fields from its methods are carried out only through the access functions.

Now we can implement the controller code:

package ua.inf.iwanoff.java.advanced.sixth;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;

public class PrimeAppController {
    @FXML private TextField textFieldTo;
    @FXML private Button buttonStart;
    @FXML private Button buttonSuspend;
    @FXML private Button buttonResume;
    @FXML private Button buttonStop;
    @FXML private ProgressBar progressBar;
    @FXML private TextArea textAreaResults;

    private PrimeNumbers primeNumbers = new PrimeNumbers(this::addToTextArea, this::setProgress, this::finish);

    @FXML
    private void startClick(ActionEvent actionEvent) {
        try {
            primeNumbers.setTo(Integer.parseInt(textFieldTo.getText()));
            textAreaResults.setText("");
            progressBar.setProgress(0);
            buttonStart.setDisable(true);
            buttonSuspend.setDisable(false);
            buttonResume.setDisable(true);
            buttonStop.setDisable(false);
            primeNumbers.start();
        } 
        catch (NumberFormatException e) {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            alert.setTitle("Error");
            alert.setHeaderText("Wrong range!");
            alert.showAndWait();
        }
    }

    @FXML
    private void suspendClick(ActionEvent actionEvent) {
        primeNumbers.suspend();
        buttonStart.setDisable(true);
        buttonSuspend.setDisable(true);
        buttonResume.setDisable(false);
        buttonStop.setDisable(false);
    }

    @FXML
    private void resumeClick(ActionEvent actionEvent) {
        primeNumbers.resume();
        buttonStart.setDisable(true);
        buttonSuspend.setDisable(false);
        buttonResume.setDisable(true);
        buttonStop.setDisable(false);
    }

    @FXML
    private void stopClick(ActionEvent actionEvent) {
        primeNumbers.stop();
    }

    private void addToTextArea() {
        textAreaResults.setText(textAreaResults.getText() + primeNumbers.getLastFound() + " ");
    }

    private void setProgress() {
        progressBar.setProgress(primeNumbers.getPercentage());
    }

    private void finish() {
        buttonStart.setDisable(false);
        buttonSuspend.setDisable(true);
        buttonResume.setDisable(true);
        buttonStop.setDisable(true);
    }

}

The addToTextArea(), setProgress() and finish() methods are not intended for direct call from the controller, but for a callback. References to these functions are passed to the PrimeNumbers constructor. Event handlers call up the appropriate PrimeNumbers class methods and change the accessibility of the buttons.

4 Exercises

  1. Create a console application that demonstrates synchronization capabilities.
  2. Create a graphical user interface program that is designed to graph arbitrary functions. For implementation, use means of interpreting JavaScript scripts.

5 Quiz

  1. How to include JavaScript code into Java code?
  2. How to compile source code programmatically?
  3. Give a definition of the process and the thread.
  4. What are the benefits of using threads?
  5. What are the ways to create threads in Java?
  6. What are the states of the thread object?
  7. When threads are terminated or suspended?
  8. What means are used for threads synchronization?
  9. In which cases is the thread blocked?
  10. What is the usage of the synchronized modifier?
  11. Which container classes are thread-safe?
  12. What is the usage of the BlockingQueue container?

 

up