Multithreading in Java

The Java Virtual Machine supports parallel computing . All calculations can be performed in the context of one or more threads. We can easily set up access to the same resource or object for multiple threads, as well as set up a thread to execute a single block of code.

Any developer needs to synchronize work with threads during read and write operations for resources that have multiple threads allocated to them.

It is important that at the time of accessing the resource you have up-to-date data so that another thread can change it and you get the most updated information. Even if we take the example of a bank account, until the money has come to it, you cannot use it, so it is important to always have up-to-date data. Java has special classes for synchronizing and managing threads.

Thread objects

It all starts with the main (main) thread, that is, at least your program already has one running thread. The main thread can create other threads using Callable or Runnable . The creation differs only in the return result, Runnable does not return a result and cannot throw a checked exception. Therefore, you get a good opportunity to build efficient work with files, but this is very dangerous and you need to be careful.

It is also possible to schedule thread execution on a separate CPU core. The system can easily move between threads and execute a specific thread with the right settings: that is, the thread that reads the data is executed first, as soon as we have data, then we pass it to the thread that is responsible for validation, after that we pass it to the thread to execute some then business logic and a new thread write them back. In such a situation, 4 threads are processing data in turn and everything will work faster than one thread. Each such stream is converted to a native OS stream, but how it will be converted depends on the JVM implementation.

The Thread class is used to create and work with threads. It has standard control mechanisms, as well as abstract ones, such as classes and collections from java.util.concurrent .

Thread Synchronization in Java

Communication is provided by sharing access to objects. This is very effective, but at the same time it is very easy to make a mistake when working. Errors come in two cases: thread interference - when another thread interferes with your thread, and memory consistency errors - memory consistency. To solve and prevent these errors, we have different synchronization methods.

Thread synchronization in Java is handled by monitors, this is a high-level mechanism that allows only one thread to execute a block of code protected by the same monitor at a time. The behavior of monitors is considered in terms of locks; one monitor - one lock.

Synchronization has several important points that you need to pay attention to. The first point is mutual exclusion - only one thread can own the monitor, thus synchronization on the monitor implies that once one thread enters a synchronized block protected by the monitor, no other thread can enter the block protected by the monitor. this monitor until the first thread exits the synchronized block. That is, multiple threads cannot access the same synchronized block at the same time.

But synchronization is not only mutual exclusion. Synchronization ensures that data written to memory before or within a synchronized block becomes visible to other threads that are synchronized on the same monitor. After exiting the block, we release the monitor and another thread can grab it and start executing this block of code.

When a new thread captures the monitor, we gain access to and the ability to execute that block of code, and at that point in time the variables will be loaded from main memory. Then we can see all the entries made visible by the previous release of the monitor.

A read-write on a field is an atomic operation if the field is either declared volatile or protected by a unique lock acquired before any read-write. But if you still encounter an error, then you get an error about reordering (changing the order, reordering). It manifests itself in incorrectly synchronized multi-threaded programs, where one thread can observe the effects that are produced by other threads.

The effect of mutual exclusion and synchronization of threads, that is, their correct operation is achieved only by entering a synchronized block or method that implicitly acquires a lock, or by explicitly obtaining a lock. We'll talk about it below. Both ways of working affect your memory and it's important not to forget about working with volatile variables.

Volatile fields in Java

If a variable is marked volatile , it is available globally. This means that if a thread accesses a volatile variable, it will get its value before using the value from the cache.

A write works like a monitor release, and a read works like a monitor capture. Access is carried out in a relation of the type “performed before”. If you figure it out, all that will be visible to thread A when it accesses a volatile variable is the variable for thread B. That is, you are guaranteed not to lose your changes from other threads.

Volatile variables are atomic, that is, when reading such a variable, the same effect is used as when obtaining a lock - the data in memory is declared invalid or incorrect, and the value of the volatile variable is again read from memory. When writing, the effect on memory is used, as well as when releasing a lock - a volatile field is written to memory.

Java Concurrent

If you want to make a super-efficient and multi-threaded application, you must use the classes from the JavaConcurrent library , which are in the java.util.concurrent package .

The library is very voluminous and has different functionality, so let's take a look at what is inside and divide it into some modules:

Java Concurrent

Concurrent Collections is a set of collections for working in a multi-threaded environment. Instead of the basic wrapper Collections.synchronizedList with blocking access to the entire collection, locks are used on data segments or wait-free algorithms are used to read data in parallel.

Queues - non-blocking and blocking queues for working in a multi-threaded environment. Non-blocking queues focus on speed and operation without blocking threads. Blocking queues are suitable for work when you need to “slow down” the Producer or Consumer threads . For example, in a situation where some of the conditions are not met, the queue is empty or full, or there is no free Consumer 'a.

Synchronizers are utility utilities for synchronizing threads. They are a powerful weapon in "parallel" computing.

Executors is a framework for more convenient and easy creation of thread pools, it is easy to set up scheduling of asynchronous tasks with obtaining results.

Locks are many flexible thread synchronization mechanisms compared to the basic synchronized , wait , notify , notifyAll .

Atomics are classes that can support atomic operations on primitives and references.