JUC Concurrency: From Future to CompletableFuture
Prerequisite Review
../3.应用程序开发/多线程 Java SE/3. Application Development/Multithreading > Implement the Callable interface Common THREAD methods
Essentially all are Thread class calls start(),start()methods to start a new thread
- Inherit
Threadthe class, overriderunthe method - Pass to
Threadthe classrunnablethe implementation class, overriderunthe method - Create
Callable(实现call方法)-->pass to FutureTask-->pass to Threador directlycall.call(): execute directly in the current threadCallablethe task (synchronous execution), this is just an ordinary method call, executed synchronously in the current thread, not multithreading (solving the traditionalRunnableinterface problem of not being able to return values and throw checked exceptions.) ExecutorsCreate a thread pool
CompletableFuture does not directly create threads, it is a wrapper around a thread pool (usually ForkJoinPool.commonPool()) , utilizes multithreading (or a thread pool) to implement asynchronous programming.
When using CompletableFuture asynchronous methods (such as supplyAsync, runAsync), it submits the task to the thread pool for execution
Java SE/3. Application Development/Multithreading > Implement the Callable interface
concurrency (concurrent) the same entity multiple events,one processing multiple tasks on a processor simultaneously (large scale data parallel processing in scientific computing and image processing
)
parallelism (parallel): different entity multiple events, multiple processors processing multiple tasks (a Web server handling multiple client requests )

parallel then concurrent
monitor : Monitor monitor, which is what is usually called a lock
JVM relies on the monitor to implement method level synchronization and synchronization of a sequence of instructions within a method
user thread : system worker thread
daemon thread : serves other threads, such as GC
isDaemon determine whether a thread is a userfalseor daemon true

CompletableFuture
CompletableFuture does not directly create threads, it is a wrapper around a thread pool (usually ForkJoinPool.commonPool()) , utilizes Multi-threading (or a thread pool) to implement asynchronous programming.
CompletableFuture is Future an enhanced version of that, reducing blocking and polling. It can accept a callback object, and when an asynchronous task completes or an exception occurs, it automatically invokes the callback method of the callback object.
Requirement: multi-threading, able to return, able to handle asynchronous tasks, which ordinary threads cannot satisfy.
Future Interface
FutureTask is Future a concrete implementation class of the interface, both representing an asynchronous computationresult
futureTaskRegarding the issue of obtaining the result
get there is a blocking problem
isDone() the method easily consumes CPU resources
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(() -> { Thread.sleep(2000); // Simulate a time consuming operation return 42; })
The polling approach above is not elegant, and the following requirements should also be met
- On completion it can actively notify
- Able to combine different asynchronous results
- Able to select the one that completes fastest
CompletionStage Interface
CompletionFuture is CompletionStage a concrete implementation class of the interface and Future of the interface, providing more functions and operation methods tohandleasynchronous computation tasks.
- Asynchronous task
finishes/error occursit automatically invokes the method of some object; - after the main thread sets up the callback, it no longer cares about the execution of the asynchronous task, and asynchronous tasks can execute in sequence.
runAsyncsupplyAsync
Not recommended to use CompatableFuture the default no-argument creation, because created this way the CompletableFuture instance does not specify a concrete asynchronous task, which may lead to logical errors or exceptions during later use. The correct approach is, when creating the CompletableFuture instance, to pass in a concrete asynchronous task, to ensure the correctness and stability of the program.

If using runAsyncthen again get it is still the previous blocking style
runAsync
thread pool similar to daemon thread will Main end immediately after the thread ends, so ensure that the main thread ends last
CompletableFuture
whenComplete
public static void main(String[] args) throws ExecutionException, InterruptedException
{
ExecutorService threadPool = Executors.newFixedThreadPool(3);
try
{
CompletableFuture.supplyAsync(
() -> {
System.out.println(Thread.currentThread().getName() + "----come in");
int result = ThreadLocalRandom.current().nextInt(10);
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----1秒钟后出结果:" + result);
if(result > 2)
{
int i=10/0;
}
return result;
},threadPool)
//v is the return value above
.whenComplete((v,e) -> {
if (e == null) {
System.out.println("-----计算完成,更新系统UpdateValue:"+v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:"+e.getCause()+"\t"+e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName()+"线程先去忙其它任务");
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
//the main thread should not end immediately, otherwise CompletableFuture the default thread pool used will close immediately:pause 3 seconds thread
//try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
}
E-commerce website price comparison, to begin with
../新特性/JDK8-17新特性#2 函数式接口
The conversion uses a multi-threading case, simulating a query
Take theliststored inList<NetMall>out and convert it toList<CompletableFuture<String>>then convert toList<String>
This uses the intermediate method map in stream Java SE/新特性/Stream流#中间方法
Convert the NetMall object into a CompletableFuture object, and return a CompletableFuture object representing the asynchronous computation result
The version not using lambda
public static List<String> getPriceByCompletableFuture(List<NetMall> list,String productName)
{
return list.stream().map(netMall -> CompletableFuture.supplyAsync(() -> String.format(productName + " in %s price is %.2f",netMall.getNetMallName(),netMall.calcPrice(productName)))).collect(Collectors.toList())//terminal method, collecting the result of the first computation into a list
.stream()//call again stream()method to convert the list into a new stream
.map(s -> s.join())//then,use map()method converts each element in the stream s performs transformation operations
.collect(Collectors.toList());//collects all objects into a list
}
CompatableFuture Common methods
runAsyncsupplyAsyncget()get()
2 Process the computation result
3 Consume the computation result
thenRun does not need the result of the previous step, will use the previous thread pool
thenRunAsync asynchronous, will useForkJoinPool, does not use a custom thread pool
thenAcceptneeds the result of the previous step, no return value, it returns a new CompletableFuture<Void>
thenApplyneeds the result of the previous step, has a return value
thenRunAsync will use ForkJoinPool, does not use a custom thread pool
join() the method blocks the current thread, waiting for the asynchronous task to finish. This ensures that where the asynchronous task result is needed, the final result can be obtained
- thenRunAsync
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {}).join());//null
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(r -> System.out.println(r)).join());//resultA and null,this is thenAccept the return result of
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(r -> System.out.println(r)));
//resultA and java.util.concurrent.CompletableFuture@76fb509a[Completed normally] this is thenAccept the return result of ,at this time, since it has not finished running, there is no result
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(r -> r + "resultB").join());//resultAresultB
4 Select based on computation speed
applyToEither
A.applyToEither(B, c -> { return c + " is winer"; });
CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {
System.out.println("A come in");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
return "playA";
});
CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
System.out.println("B come in");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
return "playB";
});
CompletableFuture<String> result = playA.applyToEither(playB, f -> {
return f + " is winer";
});
System.out.println(Thread.currentThread().getName()+"\t"+"-----: "+result.join());
5 Merge the computation results
x.thenCombine(y,(x,y)->{})
after the two CompletionStage tasks complete, the results of the two completableFuture are passed together tothenCombineto process
CompletableFuture result = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
System.out.println("-----开始两个结果合并");
return x + y;
});

lock
synchronizedmethod (lockthis)
if there are multiple inside one object synchronizedmethods, as long as one thread calls one of thesynchronizedmethods, other threads of this object can onlysynchronizedmethods can only wait that is, for the same object, only one unique thread can access thesesynchronizedmethods
if there are two objects, then it is not the same lock, so each calls its own synchronized methods, there will be no effect
static synchronizedmethod (lockclass template file)
will add a class lock, all instances will be locked
for ordinary synchronized methods, what is locked is the current instance object, usually referring to this, a specific phone, all ordinary synchronized methods use the same lock>the instance object itself
for static synchronized methods, what is locked is the current classClassobject, such asPhone.classthe unique template
synchronized
synchronized synchronized code block
why every object can become a lock
At the Java bytecode level, the implementation of the synchronized keyword relies onmonitorenterandmonitorexitthese two instructions. Every Java object implicitly has an associated monitor lock (monitor). When executing themonitorenterinstruction,the thread attempts to acquire ownership of the monitor lock associated with the object. When executing themonitorexitinstruction, the thread releases that monitor lock.
Thread execution requires first holding the monitor
Every object inherently comes with an object monitor, and every locked object is associated with a Monitor
Among them,_ownerwill point to the thread holding the lock; whoever holds it records it, whoever releases it cancels it
ReentrantLock Fair and non fair locks (default)
Fair: multiple threads acquire the lock in the order they applied for it Non fair lock: does not follow the order of lock application; a thread that applies later may acquire the lock before a thread that applied earlier. In a high concurrency environment, this may cause priority inversion or lock starvation
Why ReentrantLock non fair locks by default
The choice between fair and non fair
Non fair: throughput, saves time Fair: everyone gets an equal share
Reentrant lock (recursive lock)
JAVA microservice ecosystem/Redis7/basics/Redis advanced chapter > Reusable lock
Deadlock cases and how to troubleshoot deadlocks
Mutual exclusion condition/no preemption/hold and wait/circular wait
Two threads each want the other lock

Section
LockSupport and thread interruption
Thread interruption mechanism
interrupt/interrupted/isinterrupted
is a negotiation mechanism, not mandatory; the thread itself decides whether to interrupt
interrupt will set the interrupt status to True, and cannot interrupt the thread; it merely acts as a flag bit, and requires manual code cooperation
If the thread itself is in the sleep,wait,join state, and another thread calls interrupt set to true, an exception will be thrown interruptedException, and the exception will clear the interrupt status, changing it to false, so it is necessary to call it again in the exception handling place to ensure thread termination
interrupted: returnsthe currentthread interrupt status, and then sets the current status back tofalse
that is, if the interrupt status is True, calling interrupted returns the current thread interrupt statustrue, and then sets it tofalse
exception interruptedException after it occurs will set interrupt to false, which will cause the true set by other threads to become invalid
When there is nointerruptwhen interrupting, use the interrupt flag to interrupt
the thread that needs to be interrupted continuously monitors the interrupt status
volatilevolatile, whenvolatileis true, interrupt the thread, then change the state in another thread
AtomicBoolean
interrupt
it means setting the corresponding thread interrupt to True, and combining it with a while loop for break, which can achieve an effect similar to the above code
the underlying layer is all implemented through isInterrupted implemented
LockSupport
LockSupportis the basic thread blocking primitive used to create locks and other synchronization classes
thread wait and wakeup mechanism
Object the class methods ofwait/notify must appear in pairs in synchronized code blocks or synchronized methods
wait/notify
Java SE/3. Application Development/Multithreading > new Condition
in this example,awaitCondition the method will make the current thread wait until signalCondition the method sends a signal
Lock condition | await/signal
Object and Condition the limitation is that
- the lock must be acquired first or
lock - must wait first and then wake up
LockSupport solves the above limitation
no lock block is needed
supports waking up first and waiting later (permit design)
execute first unpark will set a permit, even if park at unpark executes later, there is already a permit, it is directly released, and one permit will be consumed
unpark will add one permit, which cannot accumulate, there is at most only one, calling multiple times unpark also has only one permit
Java Memory Model JMM
- JMM specification: The JMM specification controls data synchronization between thread working memory and main memory.
- When to synchronize data in main memory to thread working memory:
- when to flush data in thread working memory to main memory
- provides happens-before relationships: JMM defines a set of happens-before rules, these rules mandate happens-before relationships between certain operations,
JVM shields out various hardware and operating system memory access differences, JVM itself is an abstract concept, describing a set of specifications, the specification defines the read and write access methods of each variable in a program (especially multithreading) and determines when and how a write to a shared variable by one thread becomes visible to another thread, the key technical points all revolve around multithreading atomicity, visibility and ordering。
the three major characteristics of JVM (visibility, atomicity, ordering)
visibility
visibility: once changed, visible to all threads
javaordinary shared variables in do not guarantee visibility, that is to say the mainshared variables in memoryare not withshared variablescopies for synchronization, at this time the modified data needs to be synchronized to main memory, and other threads update from main memory
Modifications made by the main thread are also modifying a variable copy, not directly flushed to main memory, and also require subsequent flushing
- Atomicity: an operation cannot be interrupted, and under multithreading it cannot be interfered with by other threads
- Ordering Code executes in order from top to bottom
Ordering
Happens-before
It abstracts complex low-level memory operations and processor behavior into a set of rules we can understand (that is, how the underlying layer executes our code). These rules are the HB rules. You only need to follow these HB rules to ensure the correctness of concurrent programs, without caring about how the underlying layer implements them
If the execution of your code is not within the HB rules, it cannot be guaranteed whether the code is safe when executed
volatile and Java memory model
write volatile , the JMM immediately flushes the shared variable value in the local memory corresponding to that thread to main memory
read volatile , the JMM sets the shared variable value in the local memory corresponding to that thread to invalid, and directly reads the shared variable from main memory
That is volatile both read and write interact directly with main memory
Memory barrier
volatile The underlying implementation uses memory barriers
A memory barrier is a barrier instruction that makes the CPU or compiler perform, on the memory operations issued before and after the barrier instruction, anordering constraint, also called a memory fence or fence instruction
Memory barriers guarantee the volatile visibility of, and only guarantee volatile variables
Classification of the four major memory barrier insertion strategies
rule,XXYY XX executes first, then YY executes
StoreLoad prevents subsequent volatile read operations from being reordered, but also indirectly prevents volatile write operations (because volatile a write itself adds anotherStoreStore), and may indirectly prevent ordinary write reordering, or may not prevent it
The first operation and the second operation represent the order of precedence in the code
If reordering is not prohibited, then volatile ordinary read and write operations after a read operation may be reordered to before the volatile read operation, thereby breaking the happens-before relationship,causing program behavior to be unpredictable。
Ordinary read and write operations are very likely to depend on the volatile result of the read operation
When the second operation is a volatile write, no matter what the first operation is, they cannot be reordered. This operation ensures that operations before the volatile write are not reordered to after the volatile write
Although it is usually said that StoreLoad has the effect of preventing subsequent volatile read and write operations from being reordered to before the volatile write operation, in fact it also indirectly plays a role in preventing preceding ordinary read operations from being reordered to after the volatile write operation.
Even if no loadstore is added before the volatile write, the JVM can still guarantee program correctness in the following ways

Operations after a read are not reordered to before the read Operations before a write are not reordered to after the write
volatile write: insert avolatile writebefore and after each operationStoreStotre/Storeloadbarrier
volatile Readat eachvolatile Readinsert aLoadLoad,LoadStorebarrier after the operation
volatile Write: at eachvolatile Writeinsert aStoreStotre/Storeloadbarrier before and after the operationvolatile Readat eachvolatile Readinsert aLoadLoad,LoadStorebarrier after the operation
Case
volatile int v = 0;
int a = 0;
void example() {
v = a; // volatile Write
a = 1; // Normal write
}
In this example, there are two operations:
1. `v = a;` is a `volatile` write operation.
2. `a = 1;` is a normal write operation.
According to the rules in the table:
1. `volatile` A normal write operation after a write operation can be reordered.
2. `volatile` After the write operation, `volatile` the write operation cannot be reordered.
**as-if-serial Semantics:** No matter how reordering is done (by the compiler and processor to improve parallelism), the execution result under a single thread must not be changed.
Happens-Before Application of the rules: Although a = 1; occurs v = a; after, according to data dependency, to obtain the correct v,actually a the assignment of happens-before v = a。
Summary:
- Although JMM allows `volatile` reordering of normal write operations after a write operation, when a data dependency exists,JMM prevents this reordering.
volatile Characteristics
JVM By itself it can only guarantee the atomicity of a single command. Multiple commands can only be guaranteed by locks.
volatile Cannot guarantee atomicity, can only guarantee visibility.
Guaranteeing the atomicity of multiple commands depends on locks. The lock mechanism provides visibility and atomicity and can replace volatile
volotile Not suitable for participating in operations that depend on the current value
Visibility
volatile guarantees visibility
Without addingvolatilethe program will not stop. The modifications made by the main thread also modify a copy of the variable and are not flushed to main memory. It is also possible that they are flushed to main memory, but another thread still uses the copy and does not obtain the latest value from main memory.
No atomicity
Does not guarantee atomic operations
- Atomicity: focuses on one operation(may include multiple steps) whether it is executed as a whole, without interference from other threads.
- Prohibit reordering (
volatile): the focus is on the order between read and write operations of a single variable , as well as the order relative to othervolatilevariable read and write operations.
Instruction reordering prohibited
volatile
Prohibit operations before the write from being reordered to after the write operations from being reordered to volatile after (this means that before the volatile all write operations before the write operation have already completed and are visible to other threads), but operations after the write may be reordered, and if this has an effect then locking is needed
That is to say volatile read operations after operations will not be reordered before the read,write operations before operations have definitely completed, while lock can directly avoid all instruction reordering
volatile read after plus LoadStore and LoadLoad
volatile write before plus StoreStore, after plus Storeload
operations after the read will not be reordered before the read
operations before the write will not be reordered after the write
operations before the write from being reordered to volatile after (this means that before the volatile all write operations before the write operation have alreadycompletedand are visible to other threads), but operations after the write may be reordered, and if this has an effect then locking is needed
In the Java memory model, ordinary write operations (such as y = 1) can be reordered to volatile before the write operation. This can indeed affect the volatile expected behavior of the variable, causing the value read in other threads to be inconsistent.
Example
int x = 0;
volatile int v = 0;
int y = 0;
Thread A
x = 1; // ordinary write operation
v = y; // volatile write operation
y = 1; // ordinary write operation
Operation order in Thread A
In Thread A, we have three steps:
x = 1;(ordinary write operation)v = y;(volatilewrite operation)y = 1;(ordinary write operation)
Reordering rules
According to the Java memory model,volatilewrite operations insert memory barriers before and after them:
- Store Barrier: before writing to the
volatilevariable, ensure that all previous ordinary write operations have completed and are visible to other threads. - Load Barrier: after reading the
volatilevariable, ensure that all subsequent ordinary read operations read the latest value.
Regarding your question: can ordinary write operations be reordered before thevolatilewrite operation
In Thread A:
x = 1is an ordinary write operation.v = yis avolatilewrite operation.y = 1is an ordinary write operation.
According to the rules of the memory model:
volatileall ordinary write operations before the write operation cannot be reordered to after thevolatilewrite operation.- However,
volatileordinary write operations after the write operation can be reordered to before thevolatilewrite operation.
Specific example
Consider the following code:
x = 1; // ordinary write operation v = y; // volatile write operation y = 1; // ordinary write operation
In this case, the following reordering may occur:
x = 1andv = yorder is fixed,x = 1must be completed beforev = y.- but
y = 1may be reordered to beforev = y.
Therefore, the possible execution orders include:
x = 1y = 1v = y
Impact on Thread B
For Thread B:
int r1 = v; // volatile read operation int r2 = x; // ordinary read operation int r3 = y; // ordinary read operation
Ifr1 = y, then according to the guarantee of the memory model,r2value must be1(becausex = 1is completed beforev = y). However,r3value is uncertain, becausey = 1may be beforev = y, or may be afterv = y.
Usage
Read: volatile guarantees visibility Write: lock guarantees atomicity
A lower overhead read write lock strategy
Multithreaded usage volatile DisablereorderingAvoid problems
Object creation is divided into three steps, and multithreading may issue reordering that causes errors, so you can use volatile Disable reordering Avoid the problem
DCL double ended locking
Double check
The second locking is because another thread may not have finished creation when the check starts, but when the lock is checked it has just finished creation and released the lock
BecauseSafeDoubleCheckSingletonnot addedvolatile
singleton = new SafeDoubleCheckSingleton();
This line of code is actually divided into three steps:
- for
SafeDoubleCheckSingletonallocate memory space. - call
SafeDoubleCheckSingletonconstructor to initialize the object. - set
singletonreference to point to the allocated memory address.
Due to reordering, it may reach the second step while the object has not finished being created. Another thread judges that the object has already been created, causing an error. Therefore it is necessary tovolatileensure that reordering does not occur
Summary
Atomicity
CAS Compare and swap
The full name of CAS is Compare-And-Swap, and it is aCPU concurrency primitive. The execution of a primitive must be continuous and is not allowed to be interrupted during execution. That is to say, CAS is an atomic CPU instruction and will not cause so called data inconsistency problems
Its function is to determine whether the value at a certain memory location is the expected value. If so, change it to the new value. This process is atomic. It usesoptimisticlock retry mechanism
AtomicInteger The class mainly usesCAS (compare and swap) + volatileand native methods to ensure atomic operations, thereby avoiding synchronizedhigh overhead, and execution efficiency is greatly improved
withoutCASwhen : multiple threads need to add locks to ensure atomicity
with CAS when : multiple threads useatomic classesto ensure atomicity
but there is theABA problem
What is CAS
If the main memory is 5 and the shared variable copy of multiple threads is 5, after updating to 6 and preparing to write to main memory, it will check whether the shared variable copy is still equal to main memory. If equal, it considers that no other thread has modified it. Otherwise it will not update and will reread the value from main memory
- Guaranteed at the hardware level
It uses hardware level instructions such as the cmpxchg instruction to ensure the atomicity of the compare and update operation. This can avoid the thread blocking and context switching overhead brought by traditional locking mechanisms, thereby improving the efficiency and response speed of the system.
Source code analysis
CAS is an CPU atomic instruction, a CPU concurrency primitive, and will not cause so called data inconsistency problems
Unsafe is the core class of CAS and can directly call the operating system underlying
Spin
A thread trying to acquire the lock will not block immediately, but instead usesloop methodto try to acquire the lock,
When a thread finds that the lock is occupied, it will continuously loop to determine the lock status until it is acquired. The advantage is reducing the consumption of thread context switching. The disadvantage is that the loop willconsume CPU
java
package com.atguigu.Interview.study.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* @auther zzyy
* @create 2018-12-28 17:57
* Problem: implement a spin lock
* Spinlock benefit: the loop comparison acquires without blocking similar to wait blocking.
*
* Through CAS operation completing the spinlock,A thread first comes in and calls myLock method holds the lock by itself 5 seconds,B then comes in and finds
* currently a thread holds the lock, not null,so can only wait by spinning until A after releasing the lock B then grabs it.
*/
public class SpinLockDemo
{
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock()
{
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come in");
while(!atomicReference.compareAndSet(null,thread))
{
}
}
public void myUnLock()
{
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t myUnLock over");
}
public static void main(String[] args)
{
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.myUnLock();
},"A").start();
//pause the thread for a while, ensuring A thread before B thread starts and completes
try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnLock();
},"B").start();
}
}
CAS problems
ABA problem
When thread A modifies data in memory, thread B comes and uses the data; after B changes the data, it restores the data to its original state. Thread A checks and thinks it has not changed, so the modification succeeds, resulting in a process safety problem
Therefore the version number mechanism is referenced, AtomicStampedReference

Atomic operation classes 18 Arhat enhancement
When the number of threads increases, the advantage of using atomic operation classes becomes more obvious
Basic type atomic classes
AtomicInteger/AtomicBoolean/AtomicLong
class MyNumber
{
@Getter
private AtomicInteger atomicInteger = new AtomicInteger();//default is 0
public void addPlusPlus()
{
atomicInteger.incrementAndGet();
}
}
Java SE / 3. Application Development / Multithreading > CountDownLatch
Array type atomic classes
AtomiclntegerArray
AtomicLongArray
AtomicReferenceArray
Reference type atomic classes
AtomicReference
AtomicStampedReference Version number
AtomicMarkableReference
AtomicMarkableReference
Atomic classes for modifying object fields
Can in a thread safe way operatenon thread safe objectsome fields within
Field level atomic update
AtomicIntegerFieldUpdater In atomically updated objects int value of type field
AtomicLongFieldUpdater In atomically updated objects Long value of type field
AtomicReferenceFieldUpdater Atomic updatereference typevalue of field
all must use public volatile keyword modifier
Because the atomic classes for modifying object properties are all abstract classes, each use must use a static method newUpdater() to create an updater, and you need to set the class and property you want to update.
Cannot avoid ABA, without addingsynchronized, ensure high performance atomicity
AtomicIntegerFieldUpdater
class BankAccount//resource class
{
String bankName = "CCB";
//The updated object property must use the public volatile modifier.
public volatile int money = 0;//Amount of money
public void add()
{
money++;
}
//Because the object property modification type atomic classes are all abstract classes, every use must
//use the static method newUpdater()create an updater, and you need to set the class and property to be updated
AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");
//do not add synchronized,guarantee high performance atomicity, local minimally invasive small surgery
public void transMoney(BankAccount bankAccount)
{
fieldUpdater.getAndIncrement(bankAccount);
}
}
/**
* @auther zzyy
* Operate some fields of a non thread safe object in a thread safe way.
*
* Requirement:
* 10 threads,
* each thread transfers money 1000,
* do not use synchronized,try to use AtomicIntegerFieldUpdater to implement.
*/
public class AtomicIntegerFieldUpdaterDemo
{
public static void main(String[] args) throws InterruptedException
{
BankAccount bankAccount = new BankAccount();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <=10; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=1000; j++) {
//bankAccount.add();
bankAccount.transMoney(bankAccount);
}
} finally {
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t"+"result: "+bankAccount.money);
}
}
AtomicReferenceFieldUpdater.newUpdater(当前类,current parameter class, parameter name) Update a field in a class
referenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE)
AtomicReferenceFieldUpdater
class MyVar //resource class
{
public volatile Boolean isInit = Boolean.FALSE;
AtomicReferenceFieldUpdater<MyVar,Boolean> referenceFieldUpdater =
AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");
public void init(MyVar myVar)
{
if (referenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE))
{
System.out.println(Thread.currentThread().getName()+"\t"+"----- start init,need 2 seconds");
//pause the thread for a few seconds
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"----- over init");
}else{
System.out.println(Thread.currentThread().getName()+"\t"+"----- 已经有线程在进行初始化工作。。。。。");
}
}
}
/**
* @auther zzyy
* Requirement:
* Multiple threads concurrently call the initialization method of a class, if it has not been initialized, the initialization work will be executed,
* it is required that it can only be initialized once, and only one thread succeeds
*/
public class AtomicReferenceFieldUpdaterDemo
{
public static void main(String[] args)
{
MyVar myVar = new MyVar();
for (int i = 1; i <=5; i++) {
new Thread(() -> {
myVar.init(myVar);
},String.valueOf(i)).start();
}
}
}
In depth analysis of the principle of atomic operation enhancement classes
DoubleAccumulator Create a new adder with an initial sum of 0, then carry out a transfer with each newly passed in number, depending on the overridden applyAsLong logic
DoubleAdder
LongAccumulator
LongAdder
DoubleAdder, DoubleAccumulator and performance comparison
Comparison test, performance differs by 10 times

import javax.lang.model.element.VariableElement;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
class ClickNumber //resource class
//four calculation methods,test performance
{
int number = 0;
public synchronized void clickBySynchronized()
{
number++;
}
AtomicLong atomicLong = new AtomicLong(0);
public void clickByAtomicLong()
{
atomicLong.getAndIncrement();
}
LongAdder longAdder = new LongAdder();
public void clickByLongAdder()
{
longAdder.increment();
}
LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0);
public void clickByLongAccumulator()
{
longAccumulator.accumulate(1);
}
}
/**
* @auther zzyy
* Requirement: 50 threads, each thread 100W times, the total number of likes comes out
*/
public class AccumulatorCompareDemo
{
public static final int _1W = 10000;
public static final int threadNumber = 50;
public static void main(String[] args) throws InterruptedException
{
ClickNumber clickNumber = new ClickNumber();
long startTime;
long endTime;
CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);
startTime = System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=100 * _1W; j++) {
clickNumber.clickBySynchronized();
}
} finally {
countDownLatch1.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch1.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickBySynchronized: "+clickNumber.number);
startTime = System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=100 * _1W; j++) {
clickNumber.clickByAtomicLong();
}
} finally {
countDownLatch2.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch2.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByAtomicLong: "+clickNumber.atomicLong.get());
startTime = System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=100 * _1W; j++) {
clickNumber.clickByLongAdder();
}
} finally {
countDownLatch3.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch3.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAdder: "+clickNumber.longAdder.sum());
startTime = System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=100 * _1W; j++) {
clickNumber.clickByLongAccumulator();
}
} finally {
countDownLatch4.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch4.await();
endTime = System.currentTimeMillis();
System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAccumulator: "+clickNumber.longAccumulator.get());
}
}
underlying source code
CAS only allows one thread to successfully modify the value of a shared variable, the remaining threads will fail and retry, when concurrency is high there will be very many retries, while LongAdder internally maintains a cell array based on CAS. Each cell independently maintains a count value. Each cell can have one thread enter and then update, multiple threads can operate on different cell, finally only need to accumulate each cell to get the result
Each thread ID is hashed, the hash maps to the index of some array cell, and then the value at that index is incremented
Why LongAdder is fast
LongAdderisStriped64subclass of
Striped64source code
CASonly allows one thread to successfully modify the value of a shared variable, the remaining threads will fail and retry, when concurrency is high there will be very many retries, whileLongAdderinternally maintains aCASbasedcellarray. Eachcellindependently maintains a count value. Eachcellcan have one thread enter and then update, multiple threads can operate on differentcell, finally only need to accumulate eachcellto get the result
LongAdderequals base variable plus Cell[]array

https://www.bilibili.com/video/BV1ar4y1x727/?p=90&vd_source=17535a06cd4587318e9b67ac64afeab7
92 to 99 not read
LongAddersource code
Lock -CAS-Volatile Summary
JMM: visibility, atomicity, ordering (instruction reordering)
| Visibility | Atomicity | Ordering | |
|---|---|---|---|
| Volatile | √ | × | Partial |
| CAS | × | √ | × |
| Volatile plus CAS | √ | √ | Partial |
| Lock | √ | √ | √ |
Locking, even if the operation is on an ordinary, non volatile variable, can also guarantee visibility, atomicity, and ordering. And, in this case, indeedno needto additionally use the volatile keyword
Volatile
Volatile provides visibility, partial ordering, does not guarantee atomicity
after a readoperations are not reordered before the read
before a writeoperations are not reordered after the write
Read : volitile guarantees visibility
Write : lock guarantees atomicity
cannot guarantee ordering between multiple volatile variables
volatile int x = 0;
volatile int y = 0;
// thread 1
x = 1;
y = 2;
// thread 2
System.out.println("x = " + x + ", y = " + y);
volatile int a = 0;
int b = 0;
//thread 1
a = 1;
b = 2;
//thread 2
if (b == 2) {
System.out.println(a); // may output 0,may also output 1
}
CASCompare-And-Swap
various atomic classes, using an optimistic locking mechanism
ensure the atomicity of the compare and update operation through hardware level instructions (such as cmpxchg instructions)
usually combined with volatile to achieve visibility, and partial ordering
Without CAS : multithreading needs to add locks to guarantee atomicity
With CAS : multithreading uses atomic classes can guarantee atomicity
- Lock
biased lock-lightweight-heavyweight
A lock can directly guarantee visibility, atomicity, ordering , no need forvolatile
Usually, if there are many concurrent read and write operations and the contention is not fierce, consider using CAS plus volatile; if stronger thread safety is needed or the contention is relatively fierce, you can choose to use locks directly.
ThreadLocal : Provides a kind of thread local variable, each thread has its own copy, and there is no longer any need to use Voliatile/CAS/locks
ThreadLocal
ThreadLocal to providethread local variables. These variables differ from normal variables because each thread, when accessing the ThreadLocal instance (through its get or set method), has its own independently initialized copy of the variable.ThreadLocal The instance is usually a private static field in a class, and the purpose of using it is to associate state (for example, a user ID or transaction ID) with a thread.
Achieve that each thread has its own dedicated local variable copy (each uses its own variable without troubling others, without sharing with others, everyone has a share, each person has one copy)
Provides a kind of thread local variable, each thread has its own copy, and there is no longer any need to use Voliatile/CAS/locks
Usage method
- Initialization method
initialValue/withInitialThe two methods are consistentwithInitialIt is a new method, more concise than the former
initialValue Initialization is complex
private static final ThreadLocal<Integer> saleVolume = new ThreadLocal<>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();Direct initialization is not recommended. Default is null May throw a null pointer exception
It is recommended to use withInitial initialization, becauseThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);it only needs to be initialized once, and afterward each thread uses it with its own copy initial value, and afterward this data can be operated on, but each thread can only have one copy, and after each thread is done using its own it needs to use remove() to perform cleanup
After use, the custom ThreadLocal variable needs to be cleaned up, usually usingtry-catch-finally
It is not about ThreadLocal implemented ThreadLocal, but about ThreadLocalMap, each thread operating ThreadLocal in the set method is bound to its own current thread, so ThreadLocal it only needs to be initialized once, and only one block of space needs to be allocated, so it is often used static to modify
java
Case test where each thread records its own data
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
class House //Resource class
{
int saleCount = 0;
public synchronized void saleHouse()
{
++saleCount;
}
ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);
public void saleVolumeByThreadLocal()
{
saleVolume.set(1+saleVolume.get());
}
}
/**
* @auther zzyy
* @create 2021-12-31 15:46
* Requirement 1: 5 salespeople sell houses, and the group executives only care about the accurate statistical count of total sales.
* Requirement 2: 5 each salesperson sells a random number of houses, each with an independent sales amount, their own performance follows commissions, each eats from their own pot, each salesperson works on their own, self reliant and well fed
*/
public class ThreadLocalDemo
{
public static void main(String[] args) throws InterruptedException
{
House house = new House();
for (int i = 1; i <=5; i++) {
new Thread(() -> {
int size = new Random().nextInt(5)+1;
try {
for (int j = 1; j <=size; j++) {
house.saleHouse();
house.saleVolumeByThreadLocal();
}
System.out.println(Thread.currentThread().getName()+"\t"+"号销售卖出:"+house.saleVolume.get());
} finally {
house.saleVolume.remove();
}
},String.valueOf(i)).start();
};
//Pause milliseconds
try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套: "+house.saleCount);
}
}
Source code analysis
Thread--> ThreadLocal -->ThreadLocalMap-->Current thread and current variable
Thread Contains ThreadLocal
ThreadLocal Contains static ThreadLocalMap Class
ThreadLocalMap Contains storage for the current thread and current variable
What multiple threads can access at the same time is actually the same ThreadLocal instance, but each thread has its own ThreadLocalMap object (threadLocals)
Each thread has its own Threadlocals variable, which is a ThreadlocalMap collection, and ThreadLocal helps us put data into our own Threadlocals
Similar to the relationship of person to card to card information

Memory leak problem
ThreadLocalMap It is wrapped in two layers ThreadLocal Object
(1) The first layer of wrapping uses WeakReference<ThreadLocal<?>>to turn theThreadLocalobject into a weakly referenced object (blue box)
(2) The second layer of wrapping defines a dedicated class Entry to extendWeakReference<ThreadLocal<?>>
JVM memory and garbage collection > Revisiting References
A thread can use multiple ThreadLocal variables, so the key uses ThreadLocal,to distinguish the different thread-local variables of the same thread ThreadLocalMap stored in
Reference Review
Soft Reference It is usually used in memory-sensitive programs, for example caches use soft references. When memory is sufficient, the object is retained; when memory is insufficient, it is collected.

Weak Reference: collected as soon as discovered

Phantom Reference: As the name implies, it exists in name only. Unlike the other kinds of references, a phantom reference does not determine the lifecycle of the object.
The only purpose of setting a phantom reference association is to receive a system notification when the object is collected by the garbage collector, or to add further processing later.
After the thread finishes running Threadlocal it should be collected, while ThreadLocalMap stores ThreadLocal the value, that is, it references ThreadLocal , if it is a strong reference, it will cause Threadlocal to not be collected, causing a memory leak
Entry Weak reference ThreadLocal
Entry extends WeakReference<ThreadLocal<?>>
Therefore it needs to be designed as a weak reference, but at this point it is still incomplete
After designing it as a weak reference, ThreadLocalafter it is collected Entry the key will point to null, whilevalue the value is a strong referenceObject value;, and because it is set to null afterwards the key-value pair cannot be accessed, causing a memory leak, so a weak reference cannot 100% guarantee no memory leak, so manual cleanup is needed remove, set/get It will also check all keys that are null the Entry objects
Of course, when the thread ends Thread stopping will be collected, but the thread pool reuses threads, so the thread will not stop. In this case the leak needs attention
Class A weakly references class B : A extends WeakReference<B>
Strong reference chain Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
Why the source code uses weak references
Entry It extends WeakReference<ThreadLocal<?>>, which means it holds a ThreadLocal<?> weak reference to the object
After collection using a weak reference Entry the key will point to null
Because in thread pool scenarios, the thread pool is often reused Object value; At this point, using only weak references means there will be many entries with a null key, but the value always exists, which will also trigger a new memory leak, so remove is needed for manual cleanup
The main reason the value is a strong reference is to ensure that the value of the thread-local variable can be accessed normally while the key exists. If the value were also a weak reference, then during garbage collection, even if the key still exists, the value might be collected, which would cause unexpected behavior.
The set and get methods will check all Entry objects whose key is null

Alibaba
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.2</version>
</dependency>
Alibaba open source TransmittableThreadLocal (TTL) is an enhanced version of ThreadLocal, it can pass the parent threadcontext。
Allows the value set by the parent thread to be passed to child threads, solving the data passing problem of ThreadLocal in a thread pool environment.
Summary
ThreadLocal Significance of its appearance:
Multithreading: gives each thread its own exclusive space, reducing the use of locks
Single thread: such as storing current session or transaction information, passing information within the current thread
Single thread usage case

If set tostaticthen every thread accesses the sameThreadLocalinstance
Generally also onlynewoneThreadLocalinstance, and in multithreaded use it is still a variable exclusive to each thread
Single thread usage scenario: when during the running of a thread, data used at the beginning is needed again later, it can be stored withThreadLocalfor later use by that thread
ThreadLocal and static variables can to some extent be seen as opposite forms:
Summary:
ThreadLocalThe variable is private to the thread, and each thread has its own copy.staticThe variable is globally shared, and all threads share the same instance.removeWhenThreadLocalis no longer used, whileThreadLocalMapstill storesThreadLocalwhich prevents collection, so it is designed as a weak referenceThreadLocal, allowingThreadLocalto be collected normally, butThreadlocalMapthe key value pair in it can no longer be accessed, causing the value to be unable to be collected, so manualremove,set/getalso checks all keys that arenullof theEntryobject
javabetween multiple objects, strong and weak reference relationships are usually designed
Java memory layout and object header
JVM memory and garbage collection > Object instantiation, memory layout and access positioning
- Object header (16 bytes)
- Instance data: the attribute information of the class, the attribute information of the parent class, and if it is an array instance part, including the array length, aligned to 4 bytes
- Alignment padding: the virtual machine requires that the starting address of an object must be an integer multiple of 8 bytes. The padding data does not have to exist, it is only for byte alignment, and this part of memory is padded and aligned by 8 bytes.
Mark word (8 bytes) is 64bit
64bit various combinations can show whether there is a lock and what kind of lock is added
the last two bits represent different lock states
4bit can represent 0 to 15 the numerical generational age between them is represented by 4bit so it cannot support changing to 16

java
the type pointer is compressed, from 8 to 4
-XX:+UseCompressedClassPointers
<!--
Official website:http://openjdk.java.net/projects/code-tools/jol/
Purpose: analyze the object in JVM the size and distribution
-->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
ackage com.atguigu.juc.senior.inner.object;
import org.openjdk.jol.vm.VM;
/**
* @auther zzyy
* @create 2020-06-13 11:24
*/
public class MyObject
{
public static void main(String[] args){
//VM the detailed situation of the details
System.out.println(VM.current().details());
//the bytes allocated for all objects are 8 an integer multiple of.
System.out.println(VM.current().objectAlignment());
}
}
Sychronized and lock upgrade
Performance changes
java threads are mapped onto native operating system threads, requiring switching between user mode and kernel mode, which consumes a large amount of system resources
Kernel mode and user mode
User mode and kernel mode are terms describing different privilege levels in an operating system. User mode runs at a lower privilege level and is mainly used for ordinary applications, restricting direct access to hardware and system resources. Kernel mode runs at the highest privilege level and allows direct access to and control of all hardware and system resources, and is mainly used for the operating system kernel and critical system services.
In early versions,synchronized they are heavyweight locks, with low efficiency, because the monitor lock (monitor) depends on the underlying operating system Mutex Lock for its implementation
Java 6 later, in order to reduce the performance overhead caused by acquiring and releasing locks, lightweight locks and biased locks were introduced
Lock types and upgrade steps
In the object header Mark Word they are reused according to differences in the lock flag bits, along with the lock upgrade strategy
No lock
No lock
No lock: the initial state; after an object is instantiated, if it is not contended for by any thread, then it is in the no lock state
Although the object has been created, it has not called hashcode it will not record hash encoded
Look from the bottom right corner upward
unused

https://www.bilibili.com/video/BV1ar4y1x727?p=124&vd_source=17535a06cd4587318e9b67ac64afeab7
In the object header
Biased lock
enabled by default, but with a delay4s, and during the delay it may skip the biased lock and add a lightweight lock
In actual operation, it often happens that one thread acquires a lock many times, so in the future period it is very likely to be locked again by the same thread. Therefore, in order to reduce the overhead of acquiring the lock, the JVM stores lock information in the object header to record the ID of the thread currently holding the lock, so that when acquiring the lock in the future it can quickly determine whether it is the same thread acquiring the lock, and that thread will automatically obtain the lock on subsequent accesses
JVM Use CAS operation tothread pointer ID record it into Mark Word , and modify the bias flag to indicate that the current thread obtains the lock. The lock object becomes a biased lock (through CAS modifying the lock flag bits in the object header), literally meaning a lock that is biased toward the first thread that acquires it. After the synchronized code block finishes executing, the thread does not actively release the biased lock.
That thread no longer needs to Monitor compete for the object, while other threads still need to.
Thread scheduling is controlled by the operating system, and the execution order of threads cannot be precisely controlled, so a situation may occur where a certain thread acquires the lock multiple times.
Biased lock test
Lightweight lock
java15 biased locks will gradually be deprecated later, due to high overhead and cost
Essentially it is a spin lock CAS spin first and then block; when a thread comes to participate in lock contention, but the lock acquisition conflict time is very short
The lightweight lock is used when threadsnearly alternateexecuting synchronized blocks to improve performance
Once another thread contends and finds that the current thread ID stored in the Mark Word is not its own, it will use CAS to try to acquire the lock; if it succeeds, it replaces the ID in the Mark Word with its own, and if it fails, it spins and waits, and the biased lock upgrades to a lightweight lock
When acquiring the biased lock fails, it will upgrade to a lightweight lock, JVM it will create for each thread in the current thread stack frame a space used to store the lock record (in the JVM stack), officially known as Displaced Mark WordIf a thread acquires the lock and finds that it is a lightweight lock, it will MarkWord copy the lock to its own Displaced Mark Word. inside. Then the thread tries to use CAS to change the lock MarkWord replace with pointer to the lock record. If it succeeds, the current thread acquires the lock. If it fails, it means Mark Word has already been replaced by another thread lock record, which means it is competing with other threads for the lock. The current thread then tries to use spinning to acquire the lock.
Ensure fair competition for locks among threads
Revocation of biased locks
- After this thread finishes executing
- Another thread tries to seize it
When another thread gradually comes to compete for the lock, the biased lock can no longer be used and must be upgraded to a lightweight lock
The competing thread tries CAS to update the object header. If it fails, it waits until a global safepoint (no code is executed at this time) to revoke the biased lock.
Heavyweight lock
Spin count: Adaptive spinning is an optimization technique that, based on the current thread performance during its previous spin attempt to acquire the lock,dynamically adjuststhe number of spins for the next time. Then it upgrades to a heavyweight lock
In most cases, a lightweight lock is used instead of a heavyweight lock
Summary
In one sentence: spin first, and if that does not work, use a heavyweight lock
biased lockbiased lock:Mark Wordstores the current threadID(delay4s) In this way, when acquiring the lock in the future, it can quickly determine whether the same thread is acquiring the lock, and the thread will automatically acquire the lock on subsequent accesses After a biased lock is acquired, it is not actively released. If acquiring the biased lock fails, it upgrades to a lightweight lock This is to improve performance when only one thread executes synchronization. Modern multi-core processors often compete, so biased locks were abolishedbiased locktolightweight lockOnce another thread competes and finds thatMark Wordthe thread ID stored in it is not its own, it will use CAS to try to acquire the lock. If it succeeds, it is still a biased lock. If it fails, it waits until a global safepoint. The thread holding the biased lock revokes the original biased lock, upgrades to a lightweight lock, and copies the lockMarkWordto its ownDisplaced Mark Word. inside. Then the thread tries to useCASto change the lockMarkWordreplace withpointer to the lock record。biased lockandlightweight lockBoth do not involve switching from kernel mode to user modelightweight locklightweight lockspin lock CAS, a thread participates in lock competition, but the lock acquisitionconflict time is extremely short, the lightweight lock is for when threadsnearly alternateexecuting synchronized blocks to improve performanceto heavyweight lock Under multi-threaded competition, a biased lock may skip the lightweight lock and directly upgrade to a heavyweight lock or a lightweight lock directly upgrades to a heavyweight lock
- Multiple threads frequently compete for the same lock: When multiple threads frequently try to acquire the same lock, the biased lock is revoked and directly upgraded to a heavyweight lock.
- Thread count exceeds the threshold: The JVM has a threshold. When the number of threads trying to acquire the lock exceeds this threshold, the biased lock directly upgrades to a heavyweight lock.
JVMThis threshold is dynamically adjusted based on the intensity of thread competition and system load. Once exceeded, it upgrades
Where does the hash code go after lock upgrade?
Biased locks cannot coexist with hash codes If the hash code is calculated before using the lock, it directly upgrades to a lightweight lock If the hash code is calculated while using the lock, it will directly upgrade to a heavyweight lock
Once an object has calculated its hash code, it cannot enter the lock, otherwise it will cause encoding confusion
o.hashCode()

Lock elimination: JIT will ignore locks that do not spread shared usage to other threads
Lock coarsening: within a certain range it is all one lock, applying for one lock is enough, the compiler will perform optimization
AbstractQueuedSynchronizer of AQS
An abstract queue synchronizer, many have not grabbed the lock, at this time a queue is needed to manage these threads that still need to grab the lock
It is used to build the heavyweight basic framework of locks or other synchronizer components and the entire JUC cornerstone of the system,
through the built in FFO queue to complete the queuing work of threads acquiring resources, and through aint class variable represents the state of holding the lock
AbstractQueuedSynchronizer abbreviated as AQS
Internal structure
ReentrantLock
ReentrantLock It is the operation sync, externally retained FairSync/NofairSync
lock-->acquire-->tryAcquire-->addWaiter-->acquireQueued
Assume A needs a particularly long time, explain under this circumstance
Enqueue
lock
lockThe non fair one directly preempts with CAS, if it fails to grab it entersacquireThe fair one entersacquire
acquire
acquireThere is no actual method, the parameter is of int type, representing the reusability of the lock
Call tryAcquire
acquire is the parent classAbstractQueuedSynchronizerof, both classes are the same, it will call the overridden one of the subclasstryacquire
tryAcquire
tryAcquire(int)to grab the lock
The fair lock has one morehasQueuedPredecessors(), everything else is the same
getstatueis 1, indicating another thread is occupying it
In the non fair lock
else if (current == getExclusiveOwnerThread())
Guarantee the reusability of the lock
The only difference between the two locks is that the fair lock has one more restriction when acquiring the synchronization state:
hasQueuedPredecessors()

addWaiter
- indicating the above lock grabbing failed, entering waiting
addWaiter(Node.EXCLUSIVE)Create Node and join the waiting queue
modeThere are two kinds:
SHAREDShared waiting lock
EXCLUSIVEExclusive waiting lock
tailInitially it isnull
then it directly entersenq, it will only run once, performing initialization
enq The method only on the first addition node will be called, the second time it will not be called
tis the previous tail
node.prev=t The previous node of the current node points to the previous tail
compareAndSetTail(pred, node)Update the tail, set it to the current node
t.nextMake the previous node point to the current node
The purpose of the upper half of the code is to initialize and create an empty node, and point both the head node and the tail node to this newly created node.
Then t is no longer null, but the first virtual node, so it does not enter the first if, and enters the else
This piece of code sets the first node to enter the queue as the tail node
How does it wake up, queue, and stay stable in the queue
acquireQueued
acquireQueued(node, arg)Blocks the current thread in the waiting queue until it successfully acquires the synchronization state.
head is initially the initial default empty node, afterwards they are all the node currently using the lock
The previous one is head, indicating it is now its own turn and then it continues tryAcquire, assuming other threads are still processing now, then it does not enter
failed The variable exists to ensure that under any exception or interruption, resources can be properly released and cleaned up.
Then enter shouldParkAfterFailedAcquire regarding the state change
the initial value of the previous node waitStatus default 0, then enter compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
Node. SIGNAL to -1, changed to -1, returns flase and it jumps out, enters the next loop, and enters again shouldParkAfterFailedAcquire regarding the state change, the second entry is -1 returned true, then LockSupport.park(this); , this is the current node, blocking was performed, it will wait for the pass, this is AQS how the underlying layer makes the node stop here
Then node C enters, likewise waitStatus, the first time 0 returns false then changed to -1 returnsture, likewise set park(this)
that is, the following nodes will all change the previous waitStatus to -1, then block itself
only the one after the virtual node node or the next node of the node currently using the lock
and preempts tryacquire only if successful will it enter the blue
the nodes further back in the queue will all enter the red
At this point B is stuck here spinning
dequeue
unlock-->sync.release(1)-->tryRelease(arg)-->unparkSuccessor
sync.release(1)
tryRelease(arg)
Thread.currentThread() != getExclusiveOwnerThread()
Ensure that the thread currently calling the unlock method is the thread holding the lock.
If the current thread is not the thread holding the lock, then throw IllegalMonitorStateException an exception.
Prevent other threads from attempting to release the lock without holding it, ensuring the correctness and consistency of the lock.
state is 1, minus 1 becomes 0, then release that thread
then tryRelease(arg) has finished running, returns ture
h != null the head node is definitely notnull
h.waitStatus != 0 the head node waitStatus, as long as another thread comes to compete, it will definitely also not equal 0
unparkSuccessor
then call unparkSuccessor(h) pass inhead
again the virtual node waitStatus the previous one in -1 set to 0
at this time s is the virtual node next node
the first if,S==null is false does not enter
the second enters
if (s != null)
LockSupport.unpark(s.thread);
released the pass
after releasing the pass, parkAndCheckInterrupt, what is returned is still flase, but in for (;;) , at this time tryAcquire Assume it succeeds, it can smoothly enter the blue
(A non-fair lock does not follow first-in first-out in the queue, it may be directly grabbed by another)
setHead
Set the current latest node to head
node.thread = null;
node.prev = null;
After node successfully acquires the lock, the thread variable is no longer needed. At this point, node has become the head node of the queue, and the thread information no longer needs to be kept in the node. By setting the thread field to null, it helps the garbage collector reclaim objects that are no longer needed, thereby avoiding memory leaks.
After acquiring, the thread is no longer needed

p.next set to nullso that the next one pointed to by the previous node is set to nullat this point it can be garbage collected
Cancel flow
This is usually used to skip canceled nodes and ensure the predecessor node of the current node is a valid, uncanceled node.
take node of prev of prev assign to the current node.prev,until finding the previous node that is not canceled
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Summary
Node
Each node node contains previous and next information, the queue contains head/tail information, thus forming a queue

StampedLock
Java SE, 3. Application development, multithreading > ReentrantLock
ReentrantLock-->ReentranReadWriteLock-->StampedLock
ReentranReadWriteLock When there are many reads and few writes, there may bewrite lockstarvation, the situation of being unable to grab the lock
which led to stamped lock StampedLock
StampedLock After adopting optimistic lock acquisition, other threads trying to acquire the write lock will not be blocked. This is actually an optimization for the read lock, so after acquiring the optimistic read lock, the result still needs to be verified.

stamped lock StampedLock it allows the write lock to intervene during the reading process, avoiding write lock starvation

static StampedLock stampedLock = new StampedLock();
long stamp = stampedLock.tryOptimisticRead();
stampedLock.validate(stamp) Check whether it has changed,false represents there is a modification
stampedLock.readLock() same as the read-write lock
stampedLock.writeLock() same as the read-write lock
- Disadvantages
StampedLock Does not support reentrancy, does not start with Re
StampedLock The pessimistic read lock and write lock do not support condition variables (Condition), this also needs attention.
Use StampedLock Be sure not to call the interrupt operation, that is, do not call interrupt()method
Thinking
- In multithreading: between the execution of any two lines of code, another thread may have executed other code
- What it is, what it can do, how it came about, how it was done before it existed, which pain points it solved, which places it made convenient






































































































































































































