当前位置:网站首页>Synchronized solves problems caused by sharing

Synchronized solves problems caused by sharing

2022-07-06 08:21:00 Sit in the sunny window and drink tea alone

The problem of sharing

Small cases

The initial value of two thread pairs is 0 One of the static variables is self increasing , One does self subtraction , Do it separately 5000 Time , The result is 0 Do you ?

package cn.knightzz.example;

import lombok.extern.slf4j.Slf4j;


@SuppressWarnings("all")
@Slf4j(topic = "c.TestShareValue")
public class TestShareValue {
    

    static int value = 0;

    public static void main(String[] args) throws InterruptedException {
    

        Thread t1 = new Thread(() -> {
    

            for (int i = 0; i < 5000; i++) {
    
                value++;
                log.debug("value + 1 = {}", value);
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
    

            for (int i = 0; i < 5000; i++) {
    
                value--;
                log.debug("value - 1 = {}", value);
            }
        }, "t2");

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

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

        log.debug("value : {} ", value);
    }
}

Problem analysis

The above result may be positive 、 negative 、 zero . Why? ? because Java Autoincrement of static variables in , Self subtraction is not an atomic operation , Be thorough

Explain , Must be analyzed from bytecode

For example, for i++ for (i Is a static variable ), The actual results are as follows JVM Bytecode instruction :

getstatic i //  Get static variables i Value 
iconst_1 //  Prepare constants 1
iadd //  Self increasing 
putstatic i //  Store the modified value in a static variable i

And the corresponding i-- It's similar :

getstatic i //  Get static variables i Value 
iconst_1 //  Prepare constants 1
isub //  Self reduction 
putstatic i //  Store the modified value in a static variable i

and Java The memory model of is as follows , Complete the self increment of static variables , Self subtraction requires data exchange between main memory and working memory :

img

If it is single thread or above 8 Line code is executed sequentially ( It won't interlace ) No problem :

 Insert picture description here

In the case of negative numbers :

 Insert picture description here

 Insert picture description here

img

Critical resources

  • There is no problem for a program to run multiple threads

    • The problem is that multiple threads access shared resources
  • In fact, there is no problem for multiple threads to read shared resources

    • Instruction interleaving occurs when multiple threads read and write to shared resources , There will be problems

If there are multithreaded read and write operations on shared resources in a code block , Call this code block a critical area

    static int counter = 0;

    public void increment(){
    
        //  A critical region  
        counter++;
    }

    public void decrement(){
    
        counter--;
    }

Race condition

Multiple threads execute in a critical area , The results are unpredictable due to the different execution sequences of the code , It is called a race condition

synchronized Solution

Mutually exclusive

In order to avoid the occurrence of race conditions in the critical region , There are many ways to get there :

  • Blocking solutions :synchronized,Lock
  • Non blocking solution : Atomic variable

synchronized Object lock , At most one thread can hold at the same time by mutual exclusion Object lock , When other threads try to get the lock of this object again, they will block . This ensures that the thread with the lock can safely execute the code in the critical area , Don't worry about thread context switching

although java Mutual exclusion and synchronization in synchronized Keyword to complete , But they are different :

  • Mutual exclusion is to ensure that the race condition in the critical region occurs , Only one thread can execute critical area code at a time
  • Synchronization is due to the sequence of thread execution 、 Different order 、 A thread needs to wait for other threads to run to a certain point
synchronized( object ) //  Threads 1,  Threads 2(blocked)
{
    
     A critical region 
}

Small cases

package cn.knightzz.test;

import lombok.extern.slf4j.Slf4j;

@SuppressWarnings("all")
@Slf4j(topic = "c.TestSynchronized")
public class TestSynchronized {
    

    private static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
    
        method2();
    }

    private static void method1() throws InterruptedException {
    

        Thread t1 = new Thread(() -> {
    
            for (int i = 0; i < 50000; i++) {
    
                log.debug("counter add : {} ", counter);
                counter++;
            }
            log.debug("t1 thread end ... ");
        }, "t1");

        Thread t2 = new Thread(() -> {
    
            for (int i = 0; i < 50000; i++) {
    
                log.debug("counter sub : {} ", counter);
                counter--;
            }
            log.debug("t2 thread end ... ");
        }, "t2");

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

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

        //  The end result may be   Positive numbers ,  A negative number or  0
        log.debug("method1 : counter = {}", counter);
        //  Reset counter
        counter = 0;
    }

    private static void method2() throws InterruptedException {
    

        Thread t1 = new Thread(() -> {
    
            for (int i = 0; i < 500000; i++) {
    
                synchronized (TestSynchronized.class) {
    
                    //  Use static class objects as   lock 
                    log.debug("counter add : {} ", counter);
                    counter++;
                }
            }
            log.debug("t1 thread end ... ");
        }, "t1");

        Thread t2 = new Thread(() -> {
    
            for (int i = 0; i < 500000; i++) {
    
                synchronized (TestSynchronized.class) {
    
                    log.debug("counter sub : {} ", counter);
                    counter--;
                }
            }
            log.debug("t2 thread end ... ");
        }, "t2");

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

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

        //  The end result may be   Positive numbers ,  A negative number or  0
        log.debug("method2 : counter = {}", counter);
        //  Reset counter
        counter = 0;
    }
}

img

synchronized It can be considered as the key to a room , The critical area can be considered as a room

  • When the thread gets the lock , Will enter the room and close the door , here Other threads cannot enter the critical zone Code to execute critical area
  • As shown above , Threads 2 At the door , You will find the door closed , And threads 2 Can't get lock , So it can only be blocked , Wait for thread 1 Release the lock at the end of execution
  • Another thing to note : It's not that you can carry out it all the time after you get the lock , CPU Assign to thread 1 After the end of the time slice , Threads 1 Will be kicked out of the critical zone , Then the door was locked again .
  • At this time, the thread is still holding the lock , Wait until the thread 1 Get... Again CPU After the time slice , Code to complete critical area , After releasing the lock , Threads 2 Then you can get the lock and enter the critical area to execute the code in the critical area

The diagram is as follows :

img

Atomic understanding

 for (int i = 0; i < 50000; i++) {
    
                synchronized (TestSynchronized.class) {
    
                    //  Use static class objects as   lock 
                    log.debug("counter add : {} ", counter);
                    counter++;
                }
}

Like the code above , here synchronized The package is counter++; This line of code , The operation is counter This critical resource

counter++ stay jvm There are four step instructions :

  • get static i Get static variables i
  • iconst Prepare constants
  • iadd Self increasing
  • putstatic take i The value of is written to Static variables

Atomicity is to ensure , this The four steps are an integral whole , Before these four steps are completed , No thread can get the lock , Once these four steps are completed , Then the lock will be released , therefore t1 Threads and t2 Threads can alternately mix and print

Thinking and summary

synchronized In fact, object lock is used to ensure the atomicity of the code in the critical region , The code in the critical area is inseparable from the outside , Will not be cut by the thread

Change places to interrupt

Think about the following : ?

  1. If you put synchronized(obj) Put it in for Outside of the loop , How to understand ?-- Atomicity
  2. If t1 synchronized(obj1) and t2 synchronized(obj2) How it will work ?-- Lock object
  3. If t1 synchronized(obj) and t2 What happens if you don't add ? How to understand ?-- Lock object

problem 1 :

  1. If you put synchronized(obj) Put it in for Outside of the loop , How to understand ?-- Atomicity

img

What should be noted in the above figure is : When a counter++ perhaps counter-- After execution , The object lock will be released , That's why two threads print alternately , If you put it in for Out of the loop , that for A cycle is a whole , It's atomic , Before the end of execution , Other threads cannot get the lock , therefore At this time, print successively , Print out thread 1 in the future , Will print threads 2

We can think of the place where the critical area code is stored as a house , This house has only one door , Different critical area code resources are stored in the house

// t1 Threads 
synchronized (TestSynchronized.class) {
    
                    //  Use static class objects as   lock 
                    log.debug("counter add : {} ", counter);
                    counter++;
                }

// t2  Threads 
 synchronized (TestSynchronized.class) {
    
                    log.debug("counter sub : {} ", counter);
                    counter--;
                }

You can see the code above , All of them are TestSynchronized.class This class object , TestSynchronized.class It's a lock object , This lock object corresponds to a room ( A critical region ) , There is synchronized Critical area code of the package , Whenever the corresponding thread wants to execute the corresponding code , Get the lock first .

Back to the question , If synchronized stay for Outer package of circulation , be

  • for A loop is a critical code , When the thread acquires the lock , Will directly put for The loop is finished , The lock will release
  • Whole for The cycle will be considered as a whole , Single thread for Before the loop execution ends , Will not be disturbed by other threads
  • What you can see intuitively is The program will print first t1 perhaps t2 Output , wait until t1 perhaps t2 After execution , Will execute the code of another thread

If it is wrapped in counter++ On :

 synchronized (TestSynchronized.class) {
    
                    log.debug("counter sub : {} ", counter);
                    counter--;
}


getstatic i //  obtain i Variable 
iconst_1 //  Prepare constants 1
iadd // i  Self increasing 1
putstatic i //  take 1 Write to i

Because of the addition of synchronized , The four lines above JVM Instructions are a whole , There is no interference by other threads before these four lines of instructions have been executed , such as : Execute to **iadd** after Prepare to carry out **putstatic i** Thread context switching , Command interrupt ! , Before these four instructions are executed , Will not be interrupted by any thread

img

You can see t1 and t2 In fact, it is mixed and operated alternately

If it is wrapped in for Out of the loop :

 synchronized (TestSynchronized.class) {
    
                for (int i = 0; i < 5000; i++) {
    

                    //  Use static class objects as   lock 
                    log.debug("counter add : {} ", counter);
                    counter++;
                }
            }

Like the code above , amount to for The cycle is considered as a whole , It's atomic , that , stay for Before the end of cycle execution , No interference from other threads

img

Intuitive feeling , t1 After execution , t2 Only when the lock object is obtained, the execution begins

problem 2 :

  1. If t1 synchronized(obj1) and t2 synchronized(obj2) How it will work ?-- Lock object

If two lock objects are different , But if the same critical resource is operated , It is equivalent to no lock and no mutual exclusion :

img

As shown in the figure above : Equivalent to two rooms , Because the locks are different , A lock can be understood to correspond to a room , So when threads 1 obtain testQuestion1 Lock time of , Not with threads 2 Mutual exclusion occurs , Does not affect threads 2 obtain testQuestion2 lock , So the final result is the same as not locking

img

It can also be understood as opening another door , At the same time , Both threads can enter the room to modify critical resources

problem 3

  1. If t1 synchronized(obj) and t2 What happens if you don't add ? How to understand ?-- Lock object

If one is locked , The other one doesn't add , It's also equivalent to not adding , because t2 Threads can execute at will , You can modify the value of critical resources without obtaining locks

reflection

In fact, if you want to ensure atomicity , Must be mutually exclusive , Make sure that , All critical area codes are in one room , That is to say, you need to use the same lock

img

otherwise , As long as there is a critical area not in this room , Then atomicity cannot be guaranteed , Because the code execution of this critical area is unlimited , You can modify critical resources at will

Object oriented improvement

Put the shared variables that need to be protected into a class

package cn.knightzz.improve;

import lombok.extern.slf4j.Slf4j;

@SuppressWarnings("all")
@Slf4j(topic = "c.Room")
public class Room {
    

    private int counter = 0;

    public void increment() {
    
        //  Use the current object as the lock 
        synchronized(this) {
    
            log.debug("increment counter : {} " , counter);
            counter++;
        }
    }
    public void decrement() {
    
        //  Use the current object as the lock 
        synchronized(this) {
    
            log.debug("decrement counter : {} " , counter);
            counter--;
        }
    }

    public int getCounter() {
    
        return counter;
    }
}

Then call directly when using Room The method provided can

package cn.knightzz.improve;

import cn.knightzz.test.TestSynchronized;
import lombok.extern.slf4j.Slf4j;

/** * @author  Wang Tianci  * @title: TestObjectImprove * @projectName hm-juc-codes * @description:  Object oriented improvement  * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a> * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a> * @create: 2022-07-03 21:09 */
@SuppressWarnings("all")
@Slf4j(topic = "c.Room")
public class TestObjectImprove {
    

    public static void main(String[] args) throws InterruptedException {
    

        Room room = new Room();

        Thread t1 = new Thread(() -> {
    
            //  stay  for  In the process of loop execution ,  No interference from other threads 
            for (int i = 0; i < 5000; i++) {
    
                room.increment();
            }
            log.debug("t1 thread end ... ");
        }, "t1");

        Thread t2 = new Thread(() -> {
    
            //  stay  for  In the process of loop execution ,  No interference from other threads 
            for (int i = 0; i < 5000; i++) {
    
                room.decrement();
            }
            log.debug("t2 thread end ... ");
        }, "t2");

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

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

        log.debug("counter : {} ... ", room.getCounter());
    }
}
原网站

版权声明
本文为[Sit in the sunny window and drink tea alone]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/187/202207060810303707.html