Introduction
Multithreading has been built into Java since its earliest days. So let's take a quick look at what multithreading is about.
Let's take the official lesson from Oracle as a starting point: "
Lesson: The "Hello World!" Application ". Let's change the code of our Hello World application a little to the following:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
is an array of input parameters passed when the program starts. Let's save this code to a file with a name that matches the name of the class and the extension
.java
. Let's compile using the
javac utility :
javac HelloWorldApp.java
After that, call our code with some parameter, for example, Roger:
java HelloWorldApp Roger
Our code now has a serious flaw. If we do not pass any argument (i.e. just execute java HelloWorldApp), we will get an error:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
An exception (i.e. an error) occurred in a thread named
main
. It turns out that there are some kind of threads in Java? This is where our journey begins.
Java and threads
To understand what a thread is, you need to understand how a Java application is launched. Let's change our code as follows:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
}
}
}
Now let's compile it again using javac. Next, for convenience, we will run our Java code in a separate window. On Windows you can do this like this:
start java HelloWorldApp
. Now, using the
jps utility , let's see what information Java will tell us:
The first number is the PID or Process ID, the process identifier. What is a process?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
With the help of processes, the execution of different programs is isolated from each other: each application uses its own memory area without interfering with other programs. I advise you to read the article in more detail: "
https://habr.com/post/164487/ ". A process cannot exist without threads, so if a process exists, at least one thread exists in it. How does this happen in Java? When we run a Java program, its execution starts with the
main
. We kind of enter the program, so this special method
main
is called the entry point, or "entry point". The method
main
must always be
public static void
so that the Java Virtual Machine (JVM) can begin executing our program. See "
Why is the Java main method static? " for more details. It turns out that the java launcher (java.exe or javaw.exe) is a simple application (simple C application): it loads various DLLs, which are actually the JVM. The Java launcher makes a specific set of Java Native Interface (JNI) calls. JNI is the mechanism that bridges the world of the Java Virtual Machine and the world of C++. It turns out that launcher is not the JVM, but its loader. It knows the correct commands to execute to start the JVM. Knows how to organize all the necessary environment using JNI calls. This organization of the environment also includes the creation of a main thread, which is usually called
main
. To more clearly see what threads live in a java process, we use the
jvisualvm program , which is included in the JDK. Knowing the pid of a process, we can open data on it immediately:
jvisualvm --openpid айдипроцесса
Interestingly, each thread has its own separate area in memory allocated for the process. This memory structure is called a stack. A stack consists of frames. A frame is the point of calling a method, execution point. A frame can also be represented as a StackTraceElement (see Java API for
StackTraceElement ). You can read more about the memory allocated to each thread
here . If we look at
the Java API and search for the word Thread, we will see that there is a class
java.lang.Thread . It is this class that represents a stream in Java, and it is with this that we have to work.
java.lang.Thread
A thread in Java is represented as an instance of the class
java.lang.Thread
. It is worth immediately understanding that instances of the Thread class in Java are not threads themselves. This is just a kind of API for low-level threads that are managed by the JVM and the operating system. When we launch the JVM using the java launcher, it creates a main thread with a name
main
and several more service threads. As stated in the JavaDoc of the Thread class:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
There are 2 types of threads: daemons and non-daemons. Daemon threads are background threads (service threads) that perform some work in the background. This interesting term is a reference to “Maxwell’s demon,” which you can read more about in the Wikipedia article about “
demons .” As stated in the documentation, the JVM continues executing the program (process) until:
- The Runtime.exit method is not called
- All non-daemon threads completed their work (both without errors and with exceptions thrown)
Hence the important detail: daemon threads can be terminated on any command being executed. Therefore, the integrity of the data in them is not guaranteed. Therefore, daemon threads are suitable for some service tasks. For example, in Java there is a thread that is responsible for processing finalize methods or threads related to the Garbage Collector (GC). Each thread belongs to some group (
ThreadGroup ). And groups can enter into each other, forming some hierarchy or structure.
public static void main(String []args){
Thread currentThread = Thread.currentThread();
ThreadGroup threadGroup = currentThread.getThreadGroup();
System.out.println("Thread: " + currentThread.getName());
System.out.println("Thread Group: " + threadGroup.getName());
System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
Groups allow you to streamline the management of flows and keep track of them. In addition to groups, threads have their own exception handler. Let's look at an example:
public static void main(String []args) {
Thread th = Thread.currentThread();
th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("An error occurred: " + e.getMessage());
}
});
System.out.println(2/0);
}
Division by zero will cause an error that will be caught by the handler. If you do not specify the handler yourself, the default handler implementation will work, which will display the error stack in StdError. You can read more in the review
http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ". In addition, the thread has a priority. You can read more about priorities in the article "
Java Thread Priority in Multithreading ".
Creating a thread
As stated in the documentation, we have 2 ways to create a thread. The first is to create your heir. For example:
public class HelloWorld{
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello, World!");
}
}
public static void main(String []args){
Thread thread = new MyThread();
thread.start();
}
}
As you can see, the task is launched in the method
run
, and the thread is launched in the method
start
. They should not be confused, because... if we run the method
run
directly, no new thread will be started. It is the method
start
that asks the JVM to create a new thread. The option with a descendant from Thread is bad because we include Thread in the class hierarchy. The second disadvantage is that we are starting to violate the principle of “Sole Responsibility” SOLID, because our class becomes simultaneously responsible for both managing the thread and for some task that must be performed in this thread. Which is correct? The answer is in the very method
run
that we override:
public void run() {
if (target != null) {
target.run();
}
}
Here
target
is some
java.lang.Runnable
, which we can pass to Thread when creating an instance of the class. Therefore, we can do this:
public class HelloWorld{
public static void main(String []args){
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello, World!");
}
};
Thread thread = new Thread(task);
thread.start();
}
}
It is also
Runnable
a functional interface since Java 1.8. This allows you to write task code for threads even more beautifully:
public static void main(String []args){
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
Total
So, I hope from this story it is clear what a stream is, how they exist and what basic operations can be performed with them. In
the next part , it’s worth understanding how threads interact with each other and what their life cycle is. #Viacheslav
GO TO FULL VERSION