Java Concurrent Programming: thread safety

There are three rules for how to make a class safe in a multithreaded environment. Let me tell you:

1. State variables are not shared between threads.
2. Change the state variable to immutable.
3. Use synchronization when accessing state variables.

So you might ask, what are the state variables?

Let's first look at a class without state variables. The code example is as follows.

class Chenmo {
    public void write() {
        System.out.println("I've been looking for spring for half my life. You can smile.");
    }
}

Chenmo is a class of stateless variables. It has only one method. It has neither member variables nor class variables. Any thread accessing it will not affect the results of another thread, because no state variables are shared between the two threads. Therefore, it can be concluded that classes with stateless variables must be thread safe.

Then let's look at a class with state variables. Suppose silence (Chenmo class) writes one line of words (write())   Methods), it is necessary to make a statistics, so that it is easy to ask the publishing house for royalties. We add a statistical field for Chenmo class. The code example is as follows.

class Chenmo {
    private long count = 0;
    public void write() {
        System.out.println("I've been looking for spring for half my life. You can smile.");
        count++;
    }
}

Chenmo class can accurately count the number of trips in a single threaded environment, but not in a multi-threaded environment. Because increment operation   count++   It can be divided into three operations: reading count, adding 1 to count, and assigning the calculation result to count. In multithreading, the timing of these three operations may be chaotic, and the final count value will be smaller than the expected value.

Assuming that thread A is modifying the count variable, it is necessary to prevent thread B or thread C from using this variable, so as to ensure that thread B or thread C is in the modified state of thread A when using count.

How to prevent it? Can be in   write()   Add one to the method   synchronized   keyword. The code example is as follows.

class Chenmo {
    private long count = 0;
    public synchronized void write() {
        System.out.println("I've been looking for spring for half my life. You can smile.");
        count++;
    }
}

keyword   synchronized   Is the simplest synchronization mechanism, which can ensure that only one thread can execute at the same time   write(), which guarantees   count++   It is safe in a multithreaded environment.

When writing concurrent applications, we must maintain a correct concept, that is, first ensure that the code can run correctly, and then how to improve the performance of the code.

But as we all know, synchronized   The cost is expensive to access between multiple threads   write()   Methods are mutually exclusive. When thread B accesses, it must wait for thread A to access, which cannot reflect the core value of multithreading.

java.util.concurrent.atomic.AtomicInteger   Is an Integer class that provides atomic operations. The addition and subtraction operations it provides are thread safe. So we can modify the Chenmo class in this way. The code example is as follows.

class Chenmo {
    private AtomicInteger count = new AtomicInteger(0);
    public void write() {
        System.out.println("I've been looking for spring for half my life. You can smile.");
        count.incrementAndGet();
    }
}

write()   Method no longer needed   synchronized   Keywords keep synchronized, so there is no need to call the method in a mutually exclusive way between multiple threads, which can improve the efficiency of statistics to a certain extent.

One day, the form of Publishing House's statistics of royalties changed. Not only the number of lines but also the number of words should be counted, so Chenmo class needs to add another member variable. The code example is as follows.

class Chenmo {
    private AtomicInteger lineCount = new AtomicInteger(0);
    private AtomicInteger wordCount = new AtomicInteger(0);
    public void write() {
        String words = "In my life, I have walked the road where I need a place, walked the bridge where I need a place, seen the clouds many times, drank many kinds of wine, but only loved a person of the right age.";
        System.out.println(words);
        lineCount.incrementAndGet();
        wordCount.addAndGet(words.length());
    }
}

Do you think this code is thread safe?

It turns out that this code is not thread safe. Because lineCount and wordCount are two variables, although they are thread safe respectively, when thread A adds 1 to lineCount, it cannot be guaranteed that thread B starts adding 1 to lineCount after thread A completes wordCount statistics.

What should I do? The method is also very simple. The code example is as follows.

class Chenmo {
    private int lineCount = 0;
    private int wordCount = 0;
    public void write() {
        String words = "In my life, I have walked the road where I need a place, walked the bridge where I need a place, seen the clouds many times, drank many kinds of wine, but only loved a person of the right age.";
        System.out.println(words);

        synchronized (this) {
            lineCount++;
            wordCount++;
        }
    }
}

Lock the code of line count + + and word count + + to ensure that the two lines of code are atomic. In other words, thread B must wait for thread A to make statistics before starting.

synchronized (lock) {...}   Is a simple built-in locking mechanism provided by Java to ensure the atomicity of code blocks. The thread automatically acquires the lock before entering the locked code block, and releases the lock when exiting the code block, which can ensure that a group of statements are executed as an indivisible unit.

Keywords: Java thread

Added by heinrich on Sat, 25 Sep 2021 11:24:01 +0300