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...
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
Thread
and 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
Thread
you 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
Thread
by 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.
GO TO FULL VERSION