[Yugong series] January 2022 Java Teaching Course 63 atomicity

Posted by pharcyde0 on Sat, 15 Jan 2022 07:25:07 +0100

Article catalog

1, Atomicity

1.volatile problem

Code analysis:

package com.itheima.myvolatile;

public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("Path classmate");
        t1.start();

        MyThread2 t2 = new MyThread2();
        t2.setName("Xiao pi");
        t2.start();
    }
}
package com.itheima.myvolatile;

public class Money {
    public static int money = 100000;
}
package com.itheima.myvolatile;

public class MyThread1 extends  Thread {
    @Override
    public void run() {
        while(Money.money == 100000){

        }

        System.out.println("The marriage fund is no longer 100000");
    }
}
package com.itheima.myvolatile;

public class MyThread2 extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Money.money = 90000;
    }
}

Procedural problem: Although the girl knows that the marriage fund is 100000, when the balance of the fund changes, the girl cannot know the latest balance.

2.volatile solution

Problems in the above cases:

When thread A modifies the shared data, thread B does not get the latest value in time. If the original value is still used, there will be A problem

1. Heap memory is unique. Each thread has its own thread stack.

2. When each thread uses variables in the heap, it will first copy a copy to the variable copy.

3. In the thread, each use is obtained from the copy of the variable.

Volatile keyword: force the thread to look at the latest value of the shared area every time it is used

Code implementation: using volatile keyword to solve

package com.itheima.myvolatile;

public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("Path classmate");
        t1.start();

        MyThread2 t2 = new MyThread2();
        t2.setName("Xiao pi");
        t2.start();
    }
}
package com.itheima.myvolatile;

public class Money {
    public static volatile int money = 100000;
}
package com.itheima.myvolatile;

public class MyThread1 extends  Thread {
    @Override
    public void run() {
        while(Money.money == 100000){

        }

        System.out.println("The marriage fund is no longer 100000");
    }
}
package com.itheima.myvolatile;

public class MyThread2 extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Money.money = 90000;
    }
}

3.synchronized solution

synchronized solution:

​ 1 . Thread obtains lock

​ 2 . Empty variable copy

​ 3 . Copy the latest value of the shared variable to the variable copy

​ 4 . Execute code

​ 5. Assign the value in the modified variable copy to the shared data

​ 6 . Release lock

Code implementation:

package com.itheima.myvolatile2;

public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("Path classmate");
        t1.start();

        MyThread2 t2 = new MyThread2();
        t2.setName("Xiao pi");
        t2.start();
    }
}
package com.itheima.myvolatile2;

public class Money {
    public static Object lock = new Object();
    public static volatile int money = 100000;
}
package com.itheima.myvolatile2;

public class MyThread1 extends  Thread {
    @Override
    public void run() {
        while(true){
            synchronized (Money.lock){
                if(Money.money != 100000){
                    System.out.println("The marriage fund is no longer 100000");
                    break;
                }
            }
        }
    }
}
package com.itheima.myvolatile2;

public class MyThread2 extends Thread {
    @Override
    public void run() {
        synchronized (Money.lock) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Money.money = 90000;
        }
    }
}

4. Atomicity

Overview: the so-called atomicity means that in one operation or multiple operations, either all operations are executed and will not be interrupted by any factor, or all operations are not executed. Multiple operations are an inseparable whole.

Code implementation:

package com.itheima.threadatom;

public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();

        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}
class MyAtomThread implements Runnable {
    private volatile int count = 0; //Quantity of ice cream delivered

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //1. Read data from shared data to this thread stack
            //2. Modify the value of the variable copy in the thread stack
            //3. The value of the variable copy in the thread stack will be assigned to the shared data
            count++;
            System.out.println("Already" + count + "Ice cream");
        }
    }
}

Code summary: count + + is not an atomic operation. It may be interrupted by other threads during execution

5.volatile keyword cannot guarantee atomicity

Solution: we can add locks to the count + + operation, so the count + + operation is the code in the critical area. The code in the critical area can only be executed by one thread at a time, so count + + becomes an atomic operation.

package com.itheima.threadatom2;

public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();

        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}
class MyAtomThread implements Runnable {
    private volatile int count = 0; //Quantity of ice cream delivered
    private Object lock = new Object();

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //1. Read data from shared data to this thread stack
            //2. Modify the value of the variable copy in the thread stack
            //3. The value of the variable copy in the thread stack will be assigned to the shared data
            synchronized (lock) {
                count++;
                System.out.println("Already" + count + "Ice cream");
            }
        }
    }
}

6. Atomicity_ AtomicInteger

Overview: java from jdk1 5 began to provide java util. concurrent. Atomic package (atomic package for short). The atomic operation class in this package provides a simple, efficient and thread safe way to update a variable. Because change

There are many types of quantities, so 13 classes are provided in the Atomic package, belonging to four types of Atomic update methods, namely, Atomic update basic type, Atomic update array, Atomic update reference and Atomic update attribute (field). This time we only explain

The Atomic package provides the following three classes:

AtomicBoolean: atomic update boolean type

AtomicInteger: atomic update integer

AtomicLong: atomic update long

The above 3 classes as like as two peas are provided, so this section is only explained by AtomicInteger. The common methods of AtomicInteger are as follows:

public AtomicInteger(): 	   			 Initializes an atomic type with a default value of 0 Integer
public AtomicInteger(int initialValue):  Initializes an atomic type of the specified value Integer

int get():   			 				 Get value
int getAndIncrement():      			 Add 1 to the current value atomically. Note that the value before self increment is returned here.
int incrementAndGet():     				 Add 1 to the current value atomically. Note that the value returned here is the value after self increment.
int addAndGet(int data):				 Atomically compares the entered value with the value in the instance( AtomicInteger Inside value)Add and return the result.
int getAndSet(int value):   			 Set atomically to newValue And returns the old value.

Code implementation:

package com.itheima.threadatom3;

import java.util.concurrent.atomic.AtomicInteger;

public class MyAtomIntergerDemo1 {
//    public AtomicInteger():  	                Initializes an atomic Integer with a default value of 0
//    public AtomicInteger(int initialValue): initializes an atomic Integer with a specified value
    public static void main(String[] args) {
        AtomicInteger ac = new AtomicInteger();
        System.out.println(ac);

        AtomicInteger ac2 = new AtomicInteger(10);
        System.out.println(ac2);
    }

}
package com.itheima.threadatom3;

import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;

public class MyAtomIntergerDemo2 {
//    int get():    		 		 Get value
//    int getAndIncrement(): atomically add 1 to the current value. Note that the value before self increment is returned here.
//    int incrementAndGet(): atomically add 1 to the current value. Note that the value returned here is the value after self increment.
//    int addAndGet(int data): 	  Adds the parameter to the value in the object atomically and returns the result.
//    int getAndSet(int value): the value set atomically to newValue and returns the old value.
    public static void main(String[] args) {
//        AtomicInteger ac1 = new AtomicInteger(10);
//        System.out.println(ac1.get());

//        AtomicInteger ac2 = new AtomicInteger(10);
//        int andIncrement = ac2.getAndIncrement();
//        System.out.println(andIncrement);
//        System.out.println(ac2.get());

//        AtomicInteger ac3 = new AtomicInteger(10);
//        int i = ac3.incrementAndGet();
//        System.out.println(i);// Self increasing value
//        System.out.println(ac3.get());

//        AtomicInteger ac4 = new AtomicInteger(10);
//        int i = ac4.addAndGet(20);
//        System.out.println(i);
//        System.out.println(ac4.get());

        AtomicInteger ac5 = new AtomicInteger(100);
        int andSet = ac5.getAndSet(20);
        System.out.println(andSet);
        System.out.println(ac5.get());
    }
}

7.AtomicInteger - memory parsing

AtomicInteger principle: spin lock + CAS algorithm

CAS algorithm:

There are 3 operands (memory value V, old expected value A, value B to be modified)

When the old expected value A = = memory value, the modification is successful, and change V to B

When the old expected value A= Failed to modify the memory value at this time. No action will be taken

And retrieve the current latest value (this re acquisition action is spin)

8.AtomicInteger - source code analysis

Code implementation:

package com.itheima.threadatom4;

public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();

        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}
package com.itheima.threadatom4;

import java.util.concurrent.atomic.AtomicInteger;

public class MyAtomThread implements Runnable {
    //private volatile int count = 0; // Quantity of ice cream delivered
    //private Object lock = new Object();
    AtomicInteger ac = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //1. Read data from shared data to this thread stack
            //2. Modify the value of the variable copy in the thread stack
            //3. The value of the variable copy in the thread stack will be assigned to the shared data
            //synchronized (lock) {
//                count++;
//                ac++;
            int count = ac.incrementAndGet();
            System.out.println("Already" + count + "Ice cream");
           // }
        }
    }
}

Source code analysis:

//Self increment first, and then obtain the results after self increment
public final int incrementAndGet() {
        //+1. Results after self increment
        //this represents the current atomicInteger (value)
        //1 self increment once
        return U.getAndAddInt(this, VALUE, 1) + 1;
}

public final int getAndAddInt(Object o, long offset, int delta) {
        //v old value
        int v;
        //Spin process
        do {
            //Keep getting old values
            v = getIntVolatile(o, offset);
            //If the return value of this method is false, continue to spin
            //If the return value of this method is true, the spin ends
            //o is the memory value
            //v old value
            //v + delta modified value
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
            //Function: compare the values in memory and check whether the old values are equal. If they are equal, write the modified values to memory and return true. Indicates that the modification was successful.
            //                                 If it is not equal, the modified value cannot be written to memory, and false is returned. Indicates that the modification failed.
            //If the modification fails, continue to spin.
        return v;
}

9. Pessimistic lock and optimistic lock

The difference between synchronized and CAS:

**Similarities: * * in the case of multithreading, the security of shared data can be guaranteed.

**Differences: * * synchronized always starts from the worst point of view and thinks that each time data is obtained, others may modify it. Therefore, it will be locked before each operation to share data. (pessimistic lock)

cas is from an optimistic point of view, assuming that no one will modify the data every time it is obtained, so it will not be locked. However, when modifying shared data, you will check whether others have modified the data.

If others have modified it, I will get the latest value again.

If others haven't modified it, I can directly modify the value of shared data now (Le Guan lock)