Java concurrency basics

Introduction

With the introduction of streaming APIs in the Java8 standard, it has become an environment where concurrency programming, which tends to be complicated, becomes easier and easier. I also feel this benefit very much. While such parallel processing coding methods are getting more powerful, unfortunately this time I would like to write a review of the basics, not the latest technology. If you know it, please read it. If you are making an Android application but are not familiar with parallel processing, I wrote it with the hope that you will read it carefully.

Thread safe

Suddenly, is it okay for the following classes to be accessed by multiple threads?

public class SomeSequence {

    private int value;

    public int getNextValue() {
        return value++;
    }
}

The answer is no. In a single thread environment, there is no problem. However, in the case of a multithreaded environment, we cannot guarantee that it will work properly. Assuming that incrementing value ++ is not a single operation, but value = 1

  1. Read the current value and it will be returned (tmp = value)
  2. 1 is added to the return value (tmp + 1)
  3. Write a new value (value = tmp) I am doing something like that. For example, if there are A thread and B thread, A thread calls getNextValue, and B thread also calls getNextValue at unlucky timing (for example, A thread is processing 1-2), value is set. The same incremented value is returned. The expected result of thread B is to increment the value in thread A, so it is no longer a method that returns the expected result. This phenomenon is called ** race condition **. In this case, the specifications do not assume that the values will be duplicated, and the situation of one shortage will continue for a long time, and it will be a sad state.

The only way to deal with this problem is to synchronize.

public class SomeSequence {

    private int value;

    public synchronized int getNextValue() {
        return value++;
    }
}

If you use the synchronized method forgetNextValue (), the above problem will occur. Simply put, with synchronized, this method can only run one thread at a time. Even if threads A and B access getNextValue, thread B cannot access the methods of the class until thread A exits processing. As a result, the value can be updated in a normal state. This class ensures that it behaves correctly even when accessed by multiple threads. This is called ** thread safe **. Then, I think you should add synchronized to all of them. However, there are some pitfalls. I'll explain that later.

Atomic

There is an operation like value ++ in the sample code above, but I've found that if it's not thread-safe, calling it from multiple threads doesn't guarantee correct behavior. This operation must be ** atomic ** to make this process thread-safe without creating a race condition. Atomic is called an atomic operation, which means that it can be executed after the change operation that is being performed is surely completed without interrupting other threads. Operations such as value ++ are called ** read-modify-write ** (read-modify-write) operations, but they are likely to cause race conditions and must be performed atomically.

In addition, the following delayed initialization is also called ** check-then-act, and is likely to cause a conflicting state. In addition, the state in which multiple items such as read-modify-write and check-zen-act are combined is called ** compound action **.

public class AppComponent {
    
    private AppComponent instance = null;
    
    public AppComponent getInstance() {
        if (instance == null) {
            instance = new Instance();
        }
        return instance;
    }
}

As mentioned earlier, if thread A and thread B execute getInstance, at unlucky times they will be executed before being set in the instance field and will be executed separately on twogetInstance calls. You will receive the object. This is also called ** obsolete observation ** (old value just before the update). To execute atomically, consider ** lock **, which is the basic mechanism of Java. You can also achieve thread safety by using the existing * atomic variable class * as shown below. All actions that access getNextValue will definitely be atomic.

public class SomeSequence {

    private AtomicInteger value = new AtomicInteger(0);

    public int getNextValue() {
        return value.getAndIncrement();
    }
}

Lock

Java has a mechanism to force atomicity as a function of the language itself. This is the synchronized I wrote earlier. synchronized has two roles. ** Lock ** and ** Block **. In the syntax below, the this passed in synchronized is a reference to an object that acts as a lock (key), and a code block protected by that lock. In the sample code above, it is called a synchronized method (synchronized method) with synchronized in front of the method name, and the following is called a synchronized block (synchronized block). The example locks the entire code, so it has the same meaning as the synchronized method above.

public class SomeSequence {

    private int value;

    public int getNextValue() {
        synchronized(this) {
            return value++;
        }
    }
}

All Java objects (singleton instances and all classes) act as locks for synchronization and are supported by the language. This is called a ** unique lock ** (monitor lock). Only the thread that can acquire the lock can execute the synchronization block (method), and release the lock when the synchronization block is finished (even if an exception is thrown). If thread A has acquired the lock, thread B waits for thread A to release the lock. This is called ** mutex ** (mutual exclusive lock). If Thread A does not release the lock, Thread B will wait forever (deadlock).

By the way, will the following code be deadlocked?

public class Sequence {
    public synchronized int getNextValue() {
        return value++;
    }
}

public class LoggerSequence extends Sequence {
    public synchronized int getNextValue() {
         log.d("[in] ", "-- calling getNextValue()");
         return super.getNextValue();
    }
}

In thread A, LoggerSequence acquires a lock on the source Sequence class when it calls the synchronization method getNextValue. In addition, the super class method in the block code also tries to acquire the lock of the Sequence class. Since I have acquired the Sequence lock with Logger Sequence, I am likely to wait for a lock that can never be acquired. However, deadlock does not actually occur. Java's unique lock has the property of ** reentrant ** (reentrant), and the request succeeds when the thread holding the lock tries to acquire the same lock. The JVM keeps track of the owner thread, which increments the count each time it acquires a lock and decrements it when it breaks the lock. If the same thread tries to acquire the same lock again, the lock count will be incremented or decremented.

Also, if you lock a state variable that is accessed by multiple threads, the objects in the lock must be the same. For example, if you have code like this, it's not thread-safe at all. When thread A accesses getNextValue and performs processing, if someone accessessetObject and the lock object changes, the lock state changes and another thread can access the code block. It will be.

public class SomeSequence {

    private Object lock = new Object;
    private int value;

    pubic void setObject(Object obj) {
       lock = obj;
    }

    public int getNextValue() {
        synchronized(lock) {
            return value++;
        }
    }
}

If you want to safely implement thread safety first, why not add synchronized to all of them? I think I wrote. For example, if you have the following code: A method (block) that has been synchronized earlier can only execute one thread at a time. When threads A, B, and C access ʻaddNumber` in this class, threads B and C may wait for thread A to process, perhaps for a long time. In other words, although it is simple, the execution performance is very poor.

public class SomeService {

    private BigInteger number;
    
    public synchronized BigInteger getNumber() {
        return number;
    }

    public synchronized void addNumber() {
        number.add(BigInteger.TEN);
        someHeavyRequest();
    }
}

The code below has been modified to add synchronized only to the shared variable number and update it inside this block. Assuming that the code outside the block accesses only local variables, they are not shared by multiple threads and do not need to be synchronized. Thread A releases the lock before executing someHeavyRequest, so it does not compromise concurrency and can maintain thread safety.

public class SomeService {
    private BigInteger number;
    
    public synchronized BigInteger getNumber() {
        return number;
    }

    public void addNumber() {
        synchronized(this) {
            number.add(BigInteger.TEN);
        }
        someHeavyRequest();
    }
}

Concurrency, such as making the synchronized block finer, improves execution performance (note that the code also becomes complicated). Whenever you use a lock, you should be aware that the code inside the synchronized block does it, but acquiring and releasing a lock involves overhead, so breaking it down too much can reduce execution performance. Also, even if you want to improve execution performance, sacrificing simplicity (blocking the entire method) can compromise security, so when deciding the size of the synchronized block, it is a design goal. Trade-offs are also needed. However, it is NG to have a lock while performing processing that takes a long time (heavy calculation processing, communication, etc.).

at the end

Basically, I don't know why, but it happens once in 1000 times, such as a bug that occurs? Most of the bugs I've been talking about are parallel processing systems around here. In such a case, if you have an understanding of basic concurrency, it may or may not be a shortcut for problem solving. Multithreading related things such as Java's streaming API are powering up, but if you understand these basics, you may be able to adopt it more quickly. And please tell yourself. Well, I wrote a review of Java's concurrency. Regarding parallel processing, I am writing the basics of the basics. There are many more. Those who want to know more are old, but I recommend reading the following books.

[JAVA Parallel Processing Programming](https://www.amazon.co.jp/Java%E4%B8%A6%E8%A1%8C%E5%87%A6%E7%90%86%E3%83%97 % E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0-% E2% 80% 95% E3% 81% 9D% E3% 81% AE% E3% 80% 8C% E5% 9F% BA% E7% 9B% A4% E3% 80% 8D% E3% 81% A8% E3% 80% 8C% E6% 9C% 80% E6% 96% B0API% E3% 80% 8D% E3% 82% 92% E7% A9% B6% E3% 82% 81% E3% 82% 8B% E2% 80% 95-Brian-Goetz / dp / 4797337206)

Recommended Posts

Java concurrency basics
Java basics
Java basics
java programming basics
Java JAR basics
Object-oriented (Java) basics
Java Network Basics (Communication)
Muscle Java Basics Day 1
Basics of character operation (java)
Java
Summary of Java language basics
Java programming basics practice-switch statement
Getting Started with Java Basics
Java Development Basics ~ Exercise (Array) ~
Java concurrency I don't understand
Java
[Java11] Stream Usage Summary -Basics-
[Java basics] What is Class?
Java Performance Chapter 5 Garbage Collection Basics
Java learning (0)
Studying Java ―― 3
Java protected
[Java] Annotation
Rails basics
[Java] Module
Java array
Studying Java ―― 9
Java scratch scratch
Java tips, tips
Java methods
Java method
java (constructor)
Ruby basics
Java array
[Java] ArrayDeque
Ruby basics
java (override)
Java Day 2018
Java string
java (array)
Java static
Java serialization
java beginner 4
JAVA paid
Memorandum of new graduate SES [Java basics]
Studying Java ―― 4
Java (set)
Fragment basics
java shellsort
[Java] compareTo
Studying Java -5
JPA Basics 1
java (interface)
Concurrency Method in Java with basic example
Java memorandum
☾ Java / Collection
Java array
Studying Java ―― 1
[Java] Array
Docker basics
ViewPager basics