Causes and Solutions of Thread Safety Problems

root cause

Root cause: multi-thread preemptive execution, random scheduling.

code structure

Code structure: Multiple threads modify the same variable at the same time.

atomicity

Atom: Indivisible basic unit

If the modification operation is non -atomic , the probability of thread safety issues is relatively high.

for example:

public class Thread_demo {
    
    
    static class Counter{
    
    
        int count=0;
        public void increase(){
    
    
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        final Counter counter=new Counter();
        Thread t1=new Thread(()->{
    
    
            //count自增1000次
            for (int i = 0; i <1000 ; i++) {
    
    
                counter.increase();
            }
        });
        Thread t2=new Thread(()->{
    
    
            //count自增1000次
            for (int i = 0; i <1000 ; i++) {
    
    
                counter.increase();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

operation result:

"C:\Program Files\Java\jdk1.8.0_192\bin\java.exe" "-javaagent:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\lib\idea_rt.jar=56803:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_192\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\rt.jar;D:\Java\java\untitled\out\production\untitled" Thread_demo
1673

Process finished with exit code 0

The requirement is that the two threads each increment by 1000 times, the expected result is 2000,
but the actual running result is 1673, which is not in line with the expectation. This is a typical thread safety issue.

below forcount++operation for analysis

The ++ operation is essentially divided into three steps:

  • First read the value in the memory into the register of the CPU. (load)
  • Perform the +1 operation on the value of the CPU register. (add)
  • Write the obtained result back to memory. (save)
    These three operations are the three instructions executed on the CPU

If two threads execute count++ concurrently, it is equivalent to two sets of load add save execution. Due to the preemptive execution of threads, when any instruction is currently executed, the thread may be scheduled away, and the CPU will let others thread execution, resulting in a difference in results.

Solution: synchronized

Threads are locked by using the synchronized keyword .

If two threads try to lock the same object at the same time, one can acquire the lock successfully, and the other can only block and wait (BLOCKED), until the thread just released the lock (unlock), the current thread can lock successfully! !

1. Modification method

  • Modifying ordinary methods is equivalent to locking this
    insert image description here
 Thread t1=new Thread(()->{
    
    
     //count自增1000次
     for (int i = 0; i <1000 ; i++) {
    
    
         counter.increase();
     }
 });
 Thread t2=new Thread(()->{
    
    
     //count自增1000次
     for (int i = 0; i <1000 ; i++) {
    
    
         counter.increase();
     }
 });
        

The running result after adding keywords:

"C:\Program Files\Java\jdk1.8.0_192\bin\java.exe" "-javaagent:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\lib\idea_rt.jar=55120:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_192\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\rt.jar;D:\Java\java\untitled\out\production\untitled" Thread_demo
2000

Process finished with exit code 0

When t1 executes the increase operation, it locks the counter object.
When t2 executes the increase operation, it also tries to lock the counter, but since the counter is already occupied by t1, the locking operation here will be blocked.

2. Modify the static code block,
modify the static method, and modify the general method, the same reason

3. Decorate the code block

Note :
When modifying a normal method, the lock object is this.
When modifying a static code block, the lock object is a class object.
When modifying a code block, display/manually specify the lock object

3. Reentrant
synchronized synchronization block is reentrant for the same thread, and there will be no problem of locking itself

synchronized public void increase(){
    
    
   synchronized (this){
    
    
         count++;
   }
}

Memory Visibility Issues

One thread reads a variable, and another thread modifies the variable at the same time. The value read at this time is not necessarily the modified value. The reading thread does not perceive the change of the variable. In the final analysis, it is the compiler / The jvm made a misjudgment when optimizing in a multi-threaded environment.

for example:

public class Thread_demo2 {
    
    
    static class MyCounter{
    
    
        int flag=0;

    }

    public static void main(String[] args) throws InterruptedException {
    
    
        MyCounter myCounter=new MyCounter();

        Thread t1= new Thread(()->{
    
    
            while (myCounter.flag==0){
    
    
                //
            }
            System.out.println("t1循环结束");
        });
        Thread t2=new Thread(()->{
    
    
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入一个整数");
            myCounter.flag=scanner.nextInt();

        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

    }
}

operation result

"C:\Program Files\Java\jdk1.8.0_192\bin\java.exe" "-javaagent:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\lib\idea_rt.jar=59250:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_192\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\rt.jar;D:\Java\java\untitled\out\production\untitled" Thread_demo2
请输入一个整数
1

The expected result is that after t2 changes the flag to a non-zero value, t1 will end the loop, but the actual running result is not as expected.

The following is an analysis of the **while (myCounter.flag==0)** operation, roughly divided into two steps:
1.load, read the value of the flag in the memory into the register
2.cmp, the value of the register , compare with 0, and decide where to execute next according to the comparison result

The execution speed of the above loop is extremely fast. Before the thread t2 actually modifies the flag value, the results obtained by load are the same, and the execution speed of the load operation is much slower than that of the cmp operation. Repeat the load operation.

Solution volatile

Modify variables with the keyword volatile :
When a variable is declared "volatile", the compiler will ensure that read and write operations on the variable followstrict orderto avoid memory visibility issues.
As shown in the picture:

insert image description here
The running result after adding keywords:

"C:\Program Files\Java\jdk1.8.0_192\bin\java.exe" "-javaagent:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\lib\idea_rt.jar=54891:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_192\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\rt.jar;D:\Java\java\untitled\out\production\untitled" Thread_demo2
请输入一个整数
1
t1循环结束

Process finished with exit code 0

instruction reordering problem

Essentially, there is a bug in compiler optimization.

When executing a program, the compiler may rearrange the execution order of instructions to improve instruction-level parallelism and system performance.
Due to the existence of reordering, the execution result of the program may not be consistent with the expected result of the source code.

wait and notify

The role of wait and notify : control the execution order of multiple threads

wait will block the calling thread and
notify through notify of other threads

wait operation

  • Release the lock first ( the wait operation needs to be matched withsynchronizedto use )
  • Block waiting (WAITING state)
  • After receiving the notification, try to acquire the lock again, and after acquiring the lock, continue to execute

notify operation:

  • Pair with wait
  • The lock object of wait and the lock object of notify must be consistent , otherwise notify will have no effect

for example:

public class Thread1 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Object object=new Object();
        Thread t1=new Thread(()->{
    
    
            //这个线程负责进行等待
            System.out.println("t1:wait之前");

            try {
    
    
                synchronized (object){
    
    
                    object.wait();
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            System.out.println("t2:wait之后");
        });

        Thread t2=new Thread(()->{
    
    

            System.out.println("t2:notify之前");
            synchronized (object){
    
    
                //notify务必要获取到锁,才能进行通知
                object.notify();
            }

            System.out.println("t2:notify之后");
        });
        t1.start();
        Thread.sleep(500);
        t2.start();
    }
}

operation result:

"C:\Program Files\Java\jdk1.8.0_192\bin\java.exe" "-javaagent:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\lib\idea_rt.jar=55923:D:\Program Files\IDEA\IntelliJ IDEA Community Edition 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_192\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_192\jre\lib\rt.jar;D:\Java\java\untitled\out\production\untitled" Thread1
t1:wait之前
t2:notify之前
t2:notify之后
t2:wait之后

Process finished with exit code 0


The lock object of wait and the lock object of notify should be consistent,

As shown below:
insert image description here

The notify method is used to wake up a thread that is waiting for the same object lock.
If there are multiple threads waiting, only one of them will be woken up, and it is uncertain which thread will be woken up. The notifyAll method will wake up all waiting threads.

To determine whether a code is thread-safe, you mustAnalyze specific issues!!!

Guess you like

Origin blog.csdn.net/m0_63904107/article/details/131426383