synchronized and volatile-solve thread safety issues

Solve the count++ security problem in the previous article

To solve the problem of thread safety, you can rely on locking, such as adding synchronized and volatile, synchronized is used here

public class Blog Thread Safety Issues {
    static class Counter{
        //Set an incrase method of the Counter class of count++
        public int count = 0;
         synchronized void increase(){
            count++;
        }
    }
    public static void main(String[] args) throws InterruptedException{
        final Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increase();
            }
            //The t1 thread is expected to achieve count++1000 times
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increase();
            }
            //The t2 thread is expected to achieve count++1000 times
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

Code execution result:

Because the increase() class in Counter in the code is synchronized, this method is locked

One, synchronized

1. The characteristics of synchronized

(1) Mutual exclusion

synchronized will have a mutual exclusion effect, that is, after a thread acquires the lock of an object, other threads will not be able to acquire the lock of this object when they execute here, and will enter the blocking waiting state, and only wait until the previous thread executes When the lock of this object is released, the thread can re-participate in the competition to acquire the lock.
Enter s y n c h r o n i z e d Code blocks are equivalent to locking \color{#228B22}{When entering a synchronized code block, it is equivalent to locking} It is equivalent to locking when entering the synchronized code block
When exiting the code block is equivalent to releasing the lock. \color{#228B22}{Exiting a code block is equivalent to releasing the lock. } When exiting the code block is equivalent to releasing the lock.

(2) Refresh memory

Operation steps of count++ with synchronized:
1. Obtain the mutex of count
2. Read the latest value of count from the memory to the CPU
3.count++
4. Write the changed count value into the CPU
5. Release the mutex of count
During this process, other threads cannot perform count++ operations, so it can be seen that synchronized can also guarantee memory visibility.
Memory visibility: When a thread modifies the value of a shared variable, it can be seen by other threads in a timely manner.

(3) Reentrant

The synchronized in Java can be filled into the lock, so don't worry about the thread locking itself

2. The use of synchronized

1. Modify common methods

public class blog lock example {

    public synchronized void test1(){
        int count = 0;
        count++;
    }
}

2. Modified static method

public class blog lock example {
    public synchronized static void test2(){
        int count = 0;
        count++;
    }
}

3. Decorate the code block

public class blog lock example {

    public void test3(){
        synchronized (this){
            int count = 0;
            count++;
        }
    }
}
//At this time, the current object is locked
public class blog lock example {

    public void test4(){
        synchronized (blog lock example.class){
            
        }
    }
}
//At this time, the lock is the class object

Two.volatile

Force threads to read data in memory instead of working memory

1. Ensure memory visibility

s y n c h r o n i z e d Memory visibility can also be guaranteed \color{#FF6A6A}{synchronized can also guarantee memory visibility} synchronized can also guarantee memory visibility
Require:
1. Set a counter.count to 0
2. Thread t1: has a loop, if the counter.count is not 0, jump out of the loop, and then output "counter.count is not 0, the loop ends"
3. Thread t2: Read the value entered by the user from the keyboard and assign it to counter.count

expected outcome:
After the keyboard enters a non-zero data, it outputs "counter.count is not 0, the loop ends" almost immediately, and then the process ends, outputting "Process finished with exit code 0".

public class blog lock example {

    static class Counter{
        public int count = 0;
    }

    public static void main(String[] args) throws InterruptedException{
        Counter counter = new Counter();
        Thread t1 = new Thread(() ->{
           while(counter.count == 0){

           }
            System.out.println("counter.count If it is not 0, the loop ends");
        });
        Thread t2 = new Thread(() -> {
            Scanner sc = new Scanner(System.in);
            System.out.println("Please enter a non-zero integer");
            counter.count = sc.nextInt();
        });
        t1.start();;
        t2.start();
        t1.join();
        t2.join();

    }
}

Results of the:
"Process finished with exit code 0" will not appear because the thread has not ended

The reason for the error at this time:
What thread t1 reads is the data in its own working memory. When the data in the memory changes, t1 does not perceive it, and still reads the data in the working memory, so an error occurs.
At this time if use v o l a t i l e you can solve this problem \color{#FF6A6A}{If you use volatile at this time, you can solve this problem} At this time, if you use volatile, you can solve this problem
because it forces to read the data in memory \color{#FF6A6A}{Because it forces reading data from memory} because it forces to read the data in memory

2.volatile cannot guarantee atomicity

For example, when solving the count++ problem above, if volatile is used, it is not thread-safe.

Tags: Java Algorithm jvm

Posted by moise on Wed, 01 Mar 2023 16:57:47 +0530