JavaRush /Java Blog /Random EN /Appreciate time with streams
Andrei
Level 2

Appreciate time with streams

Published in the Random EN group

Preface. Uncle Petya

So, let's say we wanted to fill a bottle of water. A bottle and tap of Uncle Petya's water are available. Uncle Petya had a new faucet installed today, and he kept praising its beauty. Before that, he only used an old, clogged tap, so the lines at the bottling were enormous. After fumbling a little, the sound of water filling was heard from the direction of the spill, after 2 minutes the bottle is still in the filling stage, the usual queue has gathered behind us, and the image in my head is of how caring Uncle Petya selects only the best H2O molecules into our bottle. Uncle Petya, trained by life, calms down the especially aggressive ones and promises to finish as quickly as possible. Having finished with the bottle, he takes the next one and turns on the usual pressure, which does not reveal all the capabilities of the new tap. People are not happy... Appreciating time with streams - 1

Theory

Multithreading is the ability of a platform to create multiple threads within a single process. Creating and executing a thread is much simpler than creating a process, so if it is necessary to implement several parallel actions in one program, additional threads are used. In the JVM, any program runs in the main thread, and the rest are launched from it. Within the same process, threads are able to exchange data with each other. When starting a new thread, you can declare it as a user thread using the method
setDaemon(true);
such threads will automatically terminate if there are no other running threads left. Threads have work priority (the choice of priority does not guarantee that the highest priority thread will finish faster than the lower priority thread).
  • MIN_PRIORITY
  • NORM_PRIORITY (default)
  • MAX_PRIORITY
Basic methods when working with streams:
  • run()– executes the thread
  • start()– starts a thread
  • getName()– returns the thread name
  • setName()– specifies the name of the stream
  • wait()– inherited method, the thread waits for the method to be called notify()from another thread
  • notify()– inherited method, resumes a previously stopped thread
  • notifyAll()– inherited method, resumes previously stopped threads
  • sleep()– pauses the stream for a specified time
  • join()– waits for the thread to complete
  • interrupt()– interrupts thread execution
More methods can be found here. It's time to think about new threads if your program has:
  • Network access
  • File system access
  • GUI

Thread class

Threads in Java are represented as a class Threadand its descendants. The example below is a simple implementation of the stream class.
import static java.lang.System.out;

public class ExampleThread extends Thread{

    public static void main(String[] args) {
        out.println("Основной поток");
        new ExampleThread().start();
    }

    @Override
    public void run() {
        out.println("Новый поток");
    }
}
As a result we get
Основной поток
Новый поток
Here we create our class and make it a descendant of the class Thread, after which we write the main() method to launch the main thread and override the run()class method Thread. Now, having created an instance of our class and executed its inherited method, start()we will launch a new thread in which everything described in the body of the method will be executed run(). It sounds complicated, but looking at the example code everything should be clear.

Runnable Interface

Oracle also suggests implementing the interface to start a new thread Runnable, which gives us more design flexibility than the only available inheritance in the previous example (if you look at the source of the class Threadyou can see that it also implements the interface Runnable). Let's use the recommended method for creating a new thread.
import static java.lang.System.out;

public class ExampleRunnable implements Runnable {

    public static void main(String[] args) {
        out.println("Основной поток");
        new Thread(new ExampleRunnable()).start();
    }

    @Override
    public void run() {
        out.println("Новый поток");
    }
}
As a result we get
Основной поток
Новый поток
The examples are very similar, because When writing the code, we had to implement an abstract method run()described in the interface Runnable. Launching a new thread is a little different. We created an instance of the class Threadby passing a reference to an instance of our interface implementation as a parameter Runnable. It is this approach that allows you to create new threads without directly inheriting the class Thread.

Long operations

The following example will clearly show the benefits of using multiple threads. Let's say we have a simple task that requires several lengthy calculations, before this article we would have solved it in a method, main()perhaps breaking it down into separate methods for ease of perception, maybe even classes, but the essence would be the same. All operations would be performed sequentially one after another. Let's simulate heavy-weight calculations and measure their execution time.
public class ComputeClass {

    public static void main(String[] args) {
        // Узнаем стартовое время программы
        long startTime = System.currentTimeMillis();

        // Определяем долгосрочные операции
        for(double i = 0; i < 999999999; i++){
        }
        System.out.println("complete 1");
        for(double i = 0; i < 999999999; i++){
        }
        System.out.println("complete 2");
        for(double i = 0; i < 999999999; i++){
        }
        System.out.println("complete 3");

        //Вычисляем и выводим время выполнения программы
        long timeSpent = System.currentTimeMillis() - startTime;
        System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
    }
}
As a result we get
complete 1
complete 2
complete 3
программа выполнялась 9885 миллисекунд
The execution time leaves much to be desired, and all this time we are looking at a blank output screen, and the situation is very similar to the story about Uncle Petya, only now in his role we, the developers, have not taken advantage of all the capabilities of modern devices. We will improve.
public class ComputeClass {

    public static void main(String[] args) {
        // Узнаем стартовое время программы
        long startTime = System.currentTimeMillis();

        // Определяем долгосрочные операции
        new MyThread(1).start();
        new MyThread(2).start();
        for(double i = 0; i < 999999999; i++){
        }
        System.out.println("complete 3");

        //Вычисляем и выводим время выполнения программы
        long timeSpent = System.currentTimeMillis() - startTime;
        System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
    }
}

class MyThread extends Thread{
int n;

MyThread(int n){
    this.n = n;
}

    @Override
    public void run() {
        for(double i = 0; i < 999999999; i++){
        }
        System.out.println("complete " + n);
    }
}
As a result we get
complete 1
complete 2
complete 3
программа выполнялась 3466 миллисекунд
Running time has been significantly reduced (this effect may not be achieved or may even increase execution time on processors that do not support multithreading). It is worth noting that threads may end out of order, and if the developer needs predictability of actions, he must implement it independently for a specific case.

Thread groups

Threads in Java can be combined into groups; the class is used for this ThreadGroup. Groups can include both single threads and entire groups. This can be convenient if you need to interrupt flows associated, for example, with the network when the connection is lost. You can read more about groups here I hope now the topic has become clearer to you and your users will be happy.
Appreciating time with streams - 2
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION