JavaRush /Java Blog /Random EN /You Can't Spoil Java with a Thread: Part I - Threads
Viacheslav
Level 3

You Can't Spoil Java with a Thread: Part I - Threads

Published in the Random EN group

Introduction

Multithreading has been built into Java since its earliest days. So let's take a quick look at what multithreading is about. You can't ruin Java with a Thread: Part I - Threads - 1Let'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]);
    }
}
argsis 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 You can't ruin Java with a Thread: Part I - Threads - 2Our 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) {
			//Do nothing
		}
	}
}
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: You can't ruin Java with a Thread: Part I - Threads - 3The 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 mainis called the entry point, or "entry point". The method mainmust always be public static voidso 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 айдипроцесса You can't ruin Java with a Thread: Part I - Threads - 4Interestingly, 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. You can't ruin Java with a Thread: Part I - Threads - 5

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 mainand 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 rundirectly, no new thread will be started. It is the method startthat 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 runthat we override:
public void run() {
	if (target != null) {
		target.run();
	}
}
Here targetis 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 Runnablea 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
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION