JUC thread notes (1)

1. Introduction to JUC

Java 5.0 provides java.util.concurrent (JUC for short) package, which adds utility classes commonly used in concurrent programming to define custom subsystems similar to threads, including thread pools, asynchronous IO and lightweight task framework. Provide an adjustable and flexible thread pool. It also provides a Collection implementation designed for use in a multithreaded context, etc.

2. volatile keyword

2.1 Memory visibility

The Java memory model stipulates that variables shared by multiple threads are stored in the main memory. Each thread has its own independent working memory, and the thread can only access its own working memory, not the working memory of other threads. A copy of the shared variables in the main memory is stored in the working memory. The thread can only operate these shared variables by operating the copy in the working memory. After the operation is completed, it will synchronize back to the main memory. The JVM model is roughly as shown in the figure below.

Insert picture description here
The JVM model stipulates: 1) All operations of a thread on shared variables must be performed in its own memory, and cannot be directly read and written from the main memory; 2) Different threads cannot directly access variables in the working memory of other threads, and variables between threads The value transfer needs to be done through the main memory. Such a provision may lead to consequences: the thread's modification of the shared variable is not updated to the main memory immediately, or the thread is not able to instantly synchronize the latest value of the shared variable to the working memory, so that when the thread uses the value of the shared variable, The value is not up to date. This leads to memory visibility.

Visibility of memory (Memory Visibility) means that when a thread is using state of the object, and another thread at the same time modify the state, need to ensure that when a thread modifies the state of the object, other threads can see the status change occurred.

Visibility error means that when the read operation and the write operation are executed in different threads, we cannot ensure that the thread performing the read operation can see the value written by other threads in a timely manner, and sometimes it is even impossible.

public class TestVolatile {
    
    
	public static void main(String[] args) {
    
    
		ThreadDemo td = new ThreadDemo();
		new Thread(td).start();
		
		while(true){
    
    
			if(td.isFlag()){
    
    
				System.out.println("------------------");
				break;
			}
		}
		
	}
}

class ThreadDemo implements Runnable {
    
    

	private boolean flag = false;

	@Override
	public void run() {
    
    
		
		try {
    
    
			Thread.sleep(200);
		} catch (InterruptedException e) {
    
    
		}

		flag = true;
		
		System.out.println("flag=" + isFlag());

	}
	public boolean isFlag() {
    
    
		return flag;
	}
	public void setFlag(boolean flag) {
    
    
		this.flag = flag;
	}
}
//输出:
//flag=true

2.2 volatile keyword

Java provides a weaker synchronization mechanism, namely volatile variables, to ensure that other threads are notified of variable update operations. When the shared variable is declared as volatile type, when the thread modifies the variable, the value of the variable will be immediately flushed back to the main memory, and the variable cached in other threads will be invalidated, so that other threads will read the value Re-read the value from the main memory (refer to cache consistency) . Therefore, when reading a volatile variable, the latest written value is always returned.

Volatile shields the necessary code optimization (instruction reordering) in the JVM, so it is relatively inefficient

public class TestVolatile {
    
    
	public static void main(String[] args) {
    
    
		ThreadDemo td = new ThreadDemo();
		new Thread(td).start();
		
		while(true){
    
    
			if(td.isFlag()){
    
    
				System.out.println("------------------");
				break;
			}
		}
		
	}
}

class ThreadDemo implements Runnable {
    
    

	private volatile boolean flag = false;

	@Override
	public void run() {
    
    
		
		try {
    
    
			Thread.sleep(200);
		} catch (InterruptedException e) {
    
    
		}

		flag = true;
		
		System.out.println("flag=" + isFlag());

	}
	public boolean isFlag() {
    
    
		return flag;
	}
	public void setFlag(boolean flag) {
    
    
		this.flag = flag;
	}
}
//输出:
//flag=true
//------------------

The main functions of the volatile keyword are:
1. Ensure memory visibility of variables
2. Partially prevent reordering.

Volatile can be regarded as a lightweight lock, but it is somewhat different from locks:
1. For multithreading, Not a mutually exclusive relationship
2. "Atomic operation" that cannot guarantee the state of a variable

3.1 Atomic variables

3.1.1 The atomicity of i++

public class TestAtomicDemo {
    
    
	public static void main(String[] args) {
    
    
		AtomicDemo ad = new AtomicDemo();
		for (int i = 0; i < 10; i++) {
    
    
			new Thread(ad).start();
		}
	}
}
class AtomicDemo implements Runnable{
    
    
	private volatile int serialNumber = 0;
//	private AtomicInteger serialNumber = new AtomicInteger(0);
	
	@Override
	public void run() {
    
    
		try {
    
    
			Thread.sleep(200);
		} catch (InterruptedException e) {
    
    
		}
		System.out.print(getSerialNumber()+" ");
	}
	public int getSerialNumber(){
    
    
		return serialNumber++;
//		return serialNumber.getAndIncrement();
	}
}
//运行结果:
//0 4 3 2 1 0 5 6 7 8 ——> 会产生重复
//如果改为:
class AtomicDemo implements Runnable{
    
    
//	private volatile int serialNumber = 0;
	private AtomicInteger serialNumber = new AtomicInteger(0);
	
	@Override
	public void run() {
    
    
		try {
    
    
			Thread.sleep(200);
		} catch (InterruptedException e) {
    
    
		}
		System.out.print(getSerialNumber()+" ");
	}
	public int getSerialNumber(){
    
    
		return serialNumber.getAndIncrement();
	}
}
//运行结果
//1 3 2 0 4 6 5 7 8 9 ——> 不会重复

3.1.2 Atomic variables

What is the simplest and most effective way to implement a global auto-increment id? The java.util.concurrent.atomic package defines some common types of atomic variables. These atomic variables provide us with a thread-safe way to manipulate a single variable lock-free.

In fact, the classes under this package provide us with features similar to volatile variables, as well as functions such as boolean compareAndSet(expectedValue, updateValue).

It sounds incredible to realize thread safety without locks. This is actually achieved through the compare and swap instructions of the CPU. Of course, locks are not required due to hardware instruction support.

Core method: boolean compareAndSet(expectedValue, updateValue)
The name of the atomic variable class is similar to AtomicXxx, for example, the AtomicInteger class is used to represent an int variable.

The scalar atomic variable classes
AtomicInteger, AtomicLong and AtomicBoolean respectively support operations on primitive data types int, long and boolean.
When the reference variable needs to be updated atomically, the AtomicReference class is used to handle the reference data type.

Atomic array classes
There are three classes called AtomicIntegerArray, AtomicLongArray and AtomicReferenceArray, which represent an array of int, long and reference types, whose elements can be updated atomically.

3.2 CAS algorithm

Compare And Swap (Compare And Exchange) / spin / spin lock / lockless
CAS is a hardware support for concurrency, a special instruction in the processor designed for multi-processor operation, used to manage sharing Concurrent access to data.

CAS is an implementation of a lock-free non-blocking algorithm.

CAS contains 3 operands:
1. The memory value V to be read and written
2. The value A
to be compared 3. The new value B to be written
if and only if the value of V is equal to A, CAS uses the new value atomically Value B to update the value of V, otherwise nothing will be performed
.
Insert picture description here

3.2.1 ABA problem

CAS will cause the ABA problem. Thread 1 is going to use CAS to replace the value of the variable from A to B. Before that, thread 2 replaces the value of the variable from A to C and C to A, and then thread 1 executes CAS It is found that the value of the variable is still A, so the CAS succeeds. But in fact the scene at this time is different from the original. Although CAS is successful, there may be hidden problems.

The solution (version number AtomicStampedReference), the basic type simple value does not require a version number

Guess you like

Origin blog.csdn.net/hxl2585530960/article/details/108985193