Multi thread and high concurrency programming ﹣ synchronized and volatile keywords

1, synchronized keyword

Synchronized is one of the most common and simple methods to solve the concurrency problem in Java. It can ensure that the threads are mutually exclusive to access the synchronized code.

For the code block surrounded by synchronized, if the thread wants to run the code, it must enter the lock object in memory to acquire the lock before it can run. And when the second thread enters, the first thread must wait for the first thread to release the lock before applying for the lock and executing the code.

Here, the synchronized mutex locks lock not the code block, but the lock object.

synchronized use case:

package com.xsh.multithreading;

public class Demo1 {

    public void run(){
        synchronized (this){
            System.out.println(".............");
        }
    }
}

Here, the lock object of the synchronized keyword is this object, and the lock object can also be any object.

The synchronized keyword works on static methods:

package com.xsh.multithreading;

public class Demo2 {

    /**
     * Because a static method can be called without a new object, the lock of the synchronized keyword of the run1 method is the Demo2.class object
     * Equivalent to the run2() method.
     * You cannot use this as a lock here because static methods are called without a new object.
     */
    public static synchronized void run(){
        System.out.println(".............");
    }

    public static void run2(){
        synchronized (Demo2.class){
            System.out.println("...............");
        }
    }
}

Relevant knowledge points:

  1. When the data is read or written, if the write operation is synchronized and the read operation is not synchronized, it may cause dirty reading, which means that the data is read in the process of writing, and the old data that has not been changed is read.
  2. A synchronization method can call another synchronization method. A thread already has the lock of an object, and when it applies again, it will still get the lock of the object, that is to say, the lock obtained by synchronized is reentrant.
  3. It is also feasible to call the synchronization method of the parent class in the synchronization method of the subclass, and the lock objects are the same.
  4. During the execution of the program, if there is an exception, the lock will be released by default. So be careful with exceptions in synchronous business logic. If multiple threads operate on the same resource, the lock is released when one thread operates to half of the time, and other threads enter the data operation again, the data may be unsafe.
  5. If you use a custom object as a lock object for synchronization keywords, if the properties of the object change, the use of locks will not be affected. However, if the object becomes another object, the locked object will also change, so you should avoid converting the reference of the locked object to another object.
  6. Do not use string constants as lock objects.

Deadlock Case:

package com.xsh.multithreading;

public class DeadlockDemo {

    private Object o1=new Object();
    private Object o2=new Object();

    private void testo1(){
        synchronized (o1){
            System.out.println("Obtain o1 lock");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            testo2();
        }
    }
    private void testo2(){
        synchronized (o2){
            System.out.println("Obtain o2 lock");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            testo1();
        }
    }

    public static void main(String[] args) {
        DeadlockDemo deadlockDemo=new DeadlockDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                deadlockDemo.testo1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                deadlockDemo.testo2();
            }
        }).start();
    }
}

synchronized lock Optimization: synchronize as little code as possible.

package com.xsh.multithreading;

public class Demo3 {

    private Integer count;

    public synchronized void test1(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void test2(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /**
         * test2 Method to synchronize only the business code that needs to be synchronized.
         * Using fine-grained lock can shorten the time of thread contention and improve efficiency
         */
        synchronized(this){
            count++;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2, volatile keyword

volatile: make a variable visible between multiple threads.

When both threads of AB use A variable, it does not mean that every time the variable is used, it will be grabbed from memory. In java, the thread will temporarily buffer the value of the variable into A buffer, and then use the data in the buffer for operation. At this time, if thread A buffer grabs the value of A variable, then thread B modifies the value. If thread A has not refreshed the value of A variable from memory, it may cause data inconsistency.

If the variable is modified by using the volatile keyword, all threads will be notified to re grab and refresh the data.

Volatile does not guarantee the data inconsistency caused by multiple threads modifying a variable together, that is to say, volatile does not replace synchronized.

Case study:

package com.xsh.multithreading;

public class VolatileDemo {

    /*volatile*/ boolean flag=true;

    private void m(){
        System.out.println("m--start");
        while (flag){

        }
        System.out.println("m--end");
    }

    public static void main(String[] args) {
        VolatileDemo volatileDemo=new VolatileDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                volatileDemo.m();
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        volatileDemo.flag=false;
    }
}

In the above case, if the flag variable is not decorated with volatile variable, it is possible that the call of the thread to m method will always be in the while loop, and it is not known that the main thread has marked the flag variable as false.

Briefly explain the difference between volatile and synchronized:

  1. volatile is much more efficient than synchronized;
  2. volatile ensures the visibility of data, but not the atomicity of data. synchronized ensures the visibility and atomicity of data.

In short, volatile ensures that after the data is updated, other threads will read the updated data in time, and it will not be wrong to get the data from memory. However, verification at the time of data writing cannot be guaranteed.
For example, two threads get 100 data, and the first thread changes the data to 110 and writes it back. At this time, the second thread changes the data to 101 and writes it back. Then when the third thread reads the data, the data becomes 101. There is no problem in reading the data, but it can't guarantee the abnormal situation in writing.

Add: when the code to be synchronized in the program is only a relatively simple operation, we can use the AtomXXX class to help us complete.
For example, AtomicInterger, the methods of these classes are atomic in themselves, but there is no guarantee that multiple methods are atomic in continuous calls.

Multi thread interview questions:
Create a collection, insert 0-9 numbers into the collection with multi threads, and monitor the collection with another thread, and print notifications when the length of the collection is 5.

package com.xsh.multithreading;

import java.util.ArrayList;
import java.util.List;

public class Demo4 {
    private volatile List<Integer> mylist=new ArrayList<>();

    /**
     * Writing program implementation
     * A thread stores numbers 0-9 in the mylist collection
     * Another thread is responsible for listening to this set, and printing notifications when the set length is 5
     */
    public static void main(String[] args) {
        Demo4 demo4=new Demo4();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t2 start-up");
                while(true){
                    if(demo4.mylist.size()==5){
                        System.out.println("Set length is 5 marks");
                        break;
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t1 start-up");
                for (int i=0;i<=9;i++){
                    demo4.mylist.add(i);
                    System.out.println("list+"+i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"t1").start();
    }
}

Lack of parsing source code: when t1 thread adds data, t2 thread is monitoring the whole cycle, which will cause a waste of memory. Instead of dealing with the problem of multiple threads operating the collection at the same time, we use wait and notify methods to freeze and wake up the threads, and use synchronous code blocks to ensure the consistency of writing and reading data.

package com.xsh.multithreading;

import java.util.ArrayList;
import java.util.List;

public class Demo4 {
    private volatile List<Integer> mylist=new ArrayList<>();

    /**
     * Writing program implementation
     * A thread stores numbers 0-9 in the mylist collection
     * Another thread is responsible for monitoring the collection and printing the notification when the collection length is 5
     */
    public static void main(String[] args) {
        Demo4 demo4=new Demo4();
        final Object lock=new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    System.out.println("t2 start-up");
                    if(demo4.mylist.size()!=5){
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("list The length is 5.");
                    lock.notify();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
               synchronized (lock){
                   System.out.println("t1 start-up");
                   for (int i=0;i<=9;i++){
                       demo4.mylist.add(i);
                       System.out.println("list+"+i);
                       if(demo4.mylist.size()==5){
                           lock.notify();
                           try {
                               lock.wait();
                           } catch (InterruptedException e) {
                               e.printStackTrace();
                           }
                       }
                       try {
                           Thread.sleep(1000);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
               }
            }
        },"t1").start();


    }
}

Insufficient in source code parsing: the above code successfully solves the problem, but the use of wait and notify is too frequent and troublesome. On this basis, the CountDownLatch class is introduced for optimization.

package com.xsh.multithreading;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class Demo4 {
    private volatile List<Integer> mylist=new ArrayList<>();

    /**
     * Writing program implementation
     * A thread stores numbers 0-9 in the mylist collection
     * Another thread is responsible for listening to this set, and printing notifications when the set length is 5
     */
    public static void main(String[] args) {
        Demo4 demo4=new Demo4();

        CountDownLatch latch=new CountDownLatch(1);

        new Thread(new Runnable() {
            @Override
            public void run() {
                    System.out.println("t2 start-up");
                    if(demo4.mylist.size()!=5){
                        try {
                            latch.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("list The length is 5.");
                }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                   System.out.println("t1 start-up");
                   for (int i=0;i<=9;i++){
                       demo4.mylist.add(i);
                       System.out.println("list+"+i);
                       if(demo4.mylist.size()==5){
                           latch.countDown();
                       }

                       try {
                           Thread.sleep(1000);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
               }
        },"t1").start();


    }
}

Countdownlatch resolution: countdownlatch enables a thread to wait for other threads to finish running before running. It is implemented by counter. When we create this object, we give it an initial value. When a await() method is invoked in a thread, the current thread is suspended until the counter of CountDownLatch is 0.
Calling the countDown() method of CountDownLatch will count - 1 of the counter. In the above case, we will deal with the running problem of two threads. The initial count is 1. When the length of the collection is 5, we will reduce the count to 0 and let the thread printing the notification run. In this way, the thread monitoring the collection will only run once when the length of the collection is 5.

Published 23 original articles, won praise and 372 visitors
Private letter follow

Tags: Java

Posted on Mon, 09 Mar 2020 20:46:18 -0700 by rayden