Multithreading 03 Thread safety issues and some simple solutions

Preface

First of all, we introduce multi-threading to solve the huge overhead caused by creating and destroying processes multiple times. Threads can share memory and hard disk resources, etc. Here we will wonder whether they will involve some security issues when sharing these things. ?If they do not allocate their own resources independently, there will definitely be security problems. However, in this fast-paced society, improvement of efficiency is inevitable. We can only discover and solve these security problems, efficiency first. !!

Example

Below we give a piece of code. We analyze the thread safety issues and solutions caused by this code from the effects and causes of the code.

package Thread;

public class ThreadDemo17 {
    private static int count = 0;
    private static final Object lock = new Object();


    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

We create two threads here, t1 and t2, let them cooperate to complete 100,000 self-increment operations, block the main thread, and finally let the main thread collect the results.

Below I give the results of several runs

Why does running the same code always produce wrong results, and the wrong results are still different???

.

Below I will give an example of analysis

Let’s observe how many steps the count++ auto-increment operation requires.

There are actually only three steps for us here

The first step is to get the element. You can see that getstatic gets the count variable.

The second step is to increment by 1, which is the iadd operation.

The third step is putstatic, which is just a save operation.

iconst_1 actually puts 1 on the top of the operand stack in the stack area.

Since this count++ operation is not atomic, thread safety issues will arise here.

Let's simulate an unsafe use case below (the following operations abstract the above multiple operations into three operations: value acquisition, auto-increment, and save)

Assume that there is only one auto-increment here. The count in t1 is first loaded to 0. At this time, the count read by t2 is also 0. t1 finally increments and saves a 1. When t2 updates, it actually updates 1, which will cause The thread insecurity problem is solved, and the final result is 1 instead of 2, which is contrary to our ideal situation.

There are many reasons for this result. The most important one is the preemptive scheduling system of the CPU. You cannot control which thread the CPU schedules first and which command is executed before executing other commands.

solution 

Here we use locks to ensure thread security. You can understand that Zhang San is going to the toilet at this time, and the door is locked with a click. At this time, no matter how anxious you are, you can't get in and complete your task. Even at this time The thread is scheduled for you, and you are just in a blocked state, unable to complete the task.

We use thesynchronized keyword to modify it. We will find that a parameter is needed. In fact, this parameter can be used no matter what object you create. Yes, as long as the parameters of the two threads are the same object

Examples are as follows

package Test;

public class ThreadDemo1125 {
    private static int count = 0;
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10000; i++) {
                synchronized (lock){
                    count++;
                }
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10000; i++) {
                synchronized (lock){
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+ count);
    }

}

At this point, the thread is safe, but it also means that the thread has changed from fully concurrent execution to semi-concurrent and semi-serial execution.

We found that in the code, the synchronized modified code block is executed serially, but other codes are still executed concurrently, so compared to using one thread to execute completely serially, the efficiency is improved a lot. Let's take a look at the code. operation result

Guess you like

Origin blog.csdn.net/qiuqiushuibx/article/details/134616860