JUC concurrent programming - Dachang
Course description
Thread basics review
Why is multithreading so important
Hardware: Moore's law failure
Software: production requirements for asynchronous callback
start a thread
Java multithreading related concepts
User thread and daemon thread
Even the simplest HelloWord program will have two threads: user thread (main) and daemon thread (GC garbage collection thread)
CompletableFuture
Future and Callable interfaces
The Future interface defines some methods for operating asynchronous task execution, such as obtaining the execution result of asynchronous task, canceling task execution, judging whether the task is cancelled, judging whether the task execution is completed, etc.
The Callable interface defines methods that need to be implemented for tasks that need to be returned. For example, if the main thread asks a child thread to execute a task, the child thread may be time-consuming. After starting the child thread to execute the task, the main thread will do other things and get the execution results of the child task after a while.
FutureTask
Specification: system out. println(futureTask.get()); Put last
As long as the get method appears, whether the calculation is completed or not, wait for the result to complete before running the following code
Improvements to Future
Advantages of completable future:
When the asynchronous task ends, it will automatically call back the method of an object;
When an asynchronous task makes an error, it will automatically call back the method of an object;
After the callback is set by the main thread, it no longer cares about the execution of asynchronous tasks. Asynchronous tasks can be executed sequentially
Differences between set and get
There is basically no difference between set and get. join does not throw exceptions
Case: sexual price demand of e-commerce websites
Record a highlight of your project:
I have made a price comparison demand, which requires us to climb the data of other websites and look at the price of the same item on different websites. The crawler's brother provided me with some data in JOSN format. I made a HashMap myself (or in the zest of Redis). After data cleaning and ensuring that there are no duplicate data, there are about 10000 pieces of data.
For these 10000 pieces of data, the most stupid method is full-text scanning, one by one, but although this can be achieved, it is relatively slow
Later, I learned that there is a completable future in JUC, which can do asynchronous multithreading concurrency without blocking. After using it, I can optimize the performance of the website from xxx seconds to xxx seconds, which is a highlight I am proud of in technology.
Moreover, completable future uses the forkjoin thread pool by default. I have written the thread pool, threadpollactuator, and the specific parameters are defined according to my own system
package com.juc; import jdk.nashorn.internal.objects.annotations.Getter; import java.util.*; import java.util.Arrays; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * @Author: GengKY * @Date: 2021/12/29 13:29 */ class ComplateFutureNetMallDemo{ static List<NetMall> list= Arrays.asList( new NetMall("JD"), new NetMall("PDD"), new NetMall("TaoBao"), new NetMall("DangDang"), new NetMall("TianMao") ); public static List<String> getPriceByStep(List<NetMall> list,String productName){ return list.stream() .map(netMall -> String.format(productName+"in %s price is %.2f",netMall.getMallName(),netMall.calPrice(productName))) .collect(Collectors.toList()); } //List<NetMall> ---->List<completableFuture<String>> --->List<string> public static List<String> getPriceByASync(List<NetMall> list,String productName){ return list.stream() .map(netMall -> CompletableFuture.supplyAsync( () -> String.format(productName + "in %s price is %.2f", netMall.getMallName(), netMall.calPrice(productName)))) .collect(Collectors.toList()) .stream().map(CompletableFuture::join) .collect(Collectors.toList()); } public static void main(String[] args) { System.out.println("1------------"); long startTime=System.currentTimeMillis(); List<String> list1 = getPriceByStep(list, "mysql"); for (String element : list1) { System.out.println(element); } long endTime=System.currentTimeMillis(); System.out.println("Spend time: "+(endTime-startTime)+"millisecond"); System.out.println("2------------"); long startTime2=System.currentTimeMillis(); List<String> list2 = getPriceByASync(list, "mysql"); for (String element : list2) { System.out.println(element); } long endTime2=System.currentTimeMillis(); System.out.println("Spend time: "+(endTime2-startTime2)+"millisecond"); } } public class NetMall { private String mallName; public String getMallName() { return mallName; } public NetMall(String mallName){ this.mallName=mallName; } public double calPrice(String productName) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0); } }
Common methods of completable future
Obtain results and trigger calculations
Process the calculation results
Consume the calculation results
Select the calculation speed
Consolidate the calculation results
homework
Thenpose() pipeline
"Lock" in Java
Optimistic lock and pessimistic lock
What is the thread eight lock / lock
According to the scope of the lock:
No lock, with lock;
Lock block and lock method body;
Object lock, class lock
The cases of eight locks are actually reflected in three places:
It acts on the instance method, locks the current instance, and obtains the lock of the current instance before entering the step code
It acts on the code block and locks the objects configured in parentheses
It acts on the static method, locks the current class, and obtains the lock of the current class object before entering the synchronization code
Analyzing sync from the perspective of self code
Sync sync code block
Lock synchronization code block: object locks
public void method(){ Object object=new Object(); synchronized (object){ System.out.println("hello"); } }
Lock synchronization code block: class lock
public void method(){ Object object=new Object(); synchronized (object.getClass()){ System.out.println("hello"); } }
The synchronized synchronization statement block is implemented using the monitorenter and monitorexit instructions,
The monitorenter instruction points to the start position of the synchronization code block, and the monitorexit instruction indicates the end position of the synchronization code block.
When the monitorenter instruction is executed, the thread attempts to acquire the lock, that is, to acquire the ownership of the object monitor.
In the Java virtual machine (HotSpot), Monitor is implemented based on C + +, which is implemented by ObjectMonitor Implemented. An ObjectMonitor object is built into each object.
In addition, wait/notify and other methods also depend on the monitor object, which is why wait/notify and other methods can only be called in synchronized blocks or methods, otherwise Java. Net will be thrown The reason for the exception of lang.illegalmonitorstateexception.
When you execute monitorenter, you will try to obtain the lock of the object. If the lock counter is 0, it means that the lock can be obtained. After obtaining, set the lock counter to 1, that is, increase 1.
After the monitorexit instruction is executed, set the lock counter to 0, indicating that the lock is released. If the object lock acquisition fails, the current thread will block and wait until the lock is released by another thread.
sync common synchronization method
The synchronized modified method does not have the monitorenter instruction and monitorexit instruction. Instead, ACC is obtained_ Synchronized identification,
This identifier indicates that the method is a synchronous method. The JVM passes this acc_ The synchronized access flag is used to identify whether a method is declared as a synchronous method, so as to execute the corresponding synchronous call.
sync lock static synchronization method
What is the of sync lock
Monitors (English: monitors, also known as monitors) is a program structure in which multiple working threads formed by multiple subroutines (objects or modules) are mutually exclusive to access shared resources.
These shared resources are generally hardware devices or a group of variables. All operations that can be performed on shared variables are concentrated in one module. (the semaphore and its operation primitive are "encapsulated" in an object) the pipeline realizes that at most one thread is executing a subprogram of the pipeline at a point in time. The management process provides a mechanism. The management process can be regarded as a software module. It encapsulates the shared variables and the operations on these shared variables to form a functional module with a certain interface. The process can call the management process to realize process level concurrency control.
MarkWord preheating
synchronized must act on an object, so Java stores lock related information in the object's header file. The lock upgrade function mainly depends on the lock flag bit and release bias lock flag bit in MarkWord. We will deepen the later explanation of lock upgrade. At present, in order to connect the past and the future, we can first mix the following figure with a familiar look, O(∩ ∩) O
Fair lock and unfair lock
class Ticket{ private int number = 30; ReentrantLock lock = new ReentrantLock(); public void sale(){ lock.lock(); try{ if(number > 0) System.out.println(Thread.currentThread().getName()+"Sold article:\t"+(number--)+"\t Remaining:"+number); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } } public class SaleTicketDemo{ public static void main(String[] args){ Ticket ticket = new Ticket(); new Thread(() -> { for (int i = 0; i <35; i++) ticket.sale(); },"a").start(); new Thread(() -> { for (int i = 0; i <35; i++) ticket.sale(); },"b").start(); new Thread(() -> { for (int i = 0; i <35; i++) ticket.sale(); },"c").start(); } }
Why is there a fair lock / unfair lock design? Why default to unfairness?
1. There is still a time difference between restoring the suspended thread and obtaining the real lock. From the perspective of developers, this time is very small, but from the perspective of CPU, this time difference is still obvious. Therefore, unfair lock can make full use of CPU time slice and minimize CPU idle state time.
2. An important consideration when using multithreading is the overhead of thread switching. When using an unfair lock, when a thread requests the lock to obtain the synchronization state and then releases the synchronization state, it does not need to consider whether there is a precursor node, so the thread that just released the lock has a very high probability of obtaining the synchronization state again at this moment, so the overhead of the thread is reduced.
What's the problem with using fair locks?
Fair locks ensure the fairness of the queue. Unfair locks ignore this rule, so it may lead to a long queue and no chance to obtain the lock,
This is the legendary "lock hunger"
When to use fair? When to use unfair?
For higher throughput, it is obvious that unfair locking is more appropriate, because it saves a lot of thread switching time, and the throughput will naturally increase;
Otherwise, use the fair lock and everyone will use it fairly.
Reentrant lock (recursive lock)
explain:
Reentrant lock, also known as recursive lock
It means that when the same thread acquires a lock in the outer method, the inner method entering the thread will automatically acquire the lock (provided that the lock object must be the same object) and will not be blocked because it has been acquired before and has not been released.
If it is a recursive calling method with synchronized modification, the second entry of the program is blocked by itself. Isn't it a big joke? There is a cocoon.
Therefore, ReentrantLock and synchronized in Java are reentrant locks. One advantage of reentrant locks is that deadlocks can be avoided to a certain extent.
Implicit reentrant lock
It refers to a lock that can be repeated and called recursively. After the lock is used in the outer layer, it can still be used in the inner layer without deadlock. Such a lock is called a reentrant lock.
To put it simply, you can always get a lock when calling other synchronized modified methods or code blocks of this class inside a synchronized modified method or code block
In contrast to reentrant phase locking, non reentrant lock can not be called recursively, and the recursive call will deadlock.
Synchronization block
public class ReEntryLockDemo { public static void main(String[] args){ final Object objectLockA = new Object(); new Thread(() -> { synchronized (objectLockA){ System.out.println("-----Outer call"); synchronized (objectLockA){ System.out.println("-----Middle level call"); synchronized (objectLockA){ System.out.println("-----Inner call"); } } } },"a").start(); } }
Synchronization method
public class ReEntryLockDemo{ public synchronized void m1(){ System.out.println("-----m1"); m2(); } public synchronized void m2(){ System.out.println("-----m2"); m3(); } public synchronized void m3(){ System.out.println("-----m3"); } public static void main(String[] args){ ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo(); reEntryLockDemo.m1(); } }
Implementation mechanism of Synchronized reentry
Each lock object has a lock counter and a pointer to the thread holding the lock.
When monitorenter is executed, if the counter of the target lock object is zero, it indicates that it is not held by other threads. Java virtual opportunity sets the holding thread of the lock object as the current thread and increases its counter by 1.
When the counter of the target lock object is not zero, if the holding thread of the lock object is the current thread, the Java virtual machine can increase its counter by 1, otherwise it needs to wait until the holding thread releases the lock.
When monitorexit is executed, the Java virtual machine needs to decrease the counter of the lock object by 1. A zero counter indicates that the lock has been released.
Explicit reentrant lock
If the lock and unlock times are different, you can play by yourself
If multiple threads work together, others' threads will get stuck
public class ReEntryLockDemo{ static Lock lock = new ReentrantLock(); public static void main(String[] args){ new Thread(() -> { lock.lock();//1. First lock try{ System.out.println("----Outer call lock"); lock.lock();//2. Second lock try{ System.out.println("----Inner call lock"); }finally { //lock.unlock(); // Under normal circumstances, the lock must be unlocked several times //Normal operation without jamming } }finally { lock.unlock(); } },"a").start(); } }
public class ReEntryLockDemo{ static Lock lock = new ReentrantLock(); public static void main(String[] args){ new Thread(() -> { lock.lock();//1. First lock try{ System.out.println("----Outer call lock"); lock.lock();//2. Second lock try{ System.out.println("----Inner call lock"); }finally { // It is intentionally noted here that the number of locks and releases is different // Because the number of locks added and released is different, the second thread is always unable to obtain the lock, resulting in waiting. //lock.unlock(); // Under normal circumstances, the lock must be unlocked several times //The second thread gets stuck } }finally { lock.unlock(); } },"a").start(); new Thread(() -> { lock.lock(); try{ System.out.println("b thread----Outer call lock"); }finally { lock.unlock(); } },"b").start(); } }
deadlock
Deadlock refers to the phenomenon that two or more threads wait for each other due to competing for resources during execution. If there is no external interference, they will not be able to move forward. If the system resources are sufficient and the resource requests of the process can be met, the possibility of deadlock is very low, otherwise they will fall into deadlock due to competing for limited resources.
Write a deadlock case
public class DeadLockDemo{ public static void main(String[] args){ final Object objectLockA = new Object(); final Object objectLockB = new Object(); new Thread(() -> { synchronized (objectLockA){ System.out.println(Thread.currentThread().getName()+"\t"+"Own holding A,Hope to obtain B"); //Pause the thread for a few seconds try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (objectLockB){ System.out.println(Thread.currentThread().getName()+"\t"+"A-------Already obtained B"); } } },"A").start(); new Thread(() -> { synchronized (objectLockB){ System.out.println(Thread.currentThread().getName()+"\t"+"Own holding B,Hope to obtain A"); //Pause the thread for a few seconds try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (objectLockA){ System.out.println(Thread.currentThread().getName()+"\t"+"B-------Already obtained A"); } } },"B").start(); } }
How to prove deadlock
Call up the console using the jconsole command
Using jps -l jstack thread ID
Some other locks
Thread interrupt mechanism
Interview question feedback
What is an interrupt
Method of API related to interrupt
How to stop a thread using the interrupt flag
Through a volatile variable
Through AtomicBoolean
It is implemented through the interrupt api method of Thread class
interrupt source code analysis
If a blocked thread (wait(), join(), sleep()) is an interrupt, the interrupt flag bit will be cleared (reset to false) and an InterruptedException exception will be thrown
isInterrupted source code analysis
Does Interrupt stop immediately
The following code shows that when Interrupt is called on a thread, it does not stop immediately
Backhand case
Second hand case-1
Execution results:
The program is executed normally, and the hello interrupt is printed continuously within 3 seconds. After 3 seconds, the "* * * program end" is printed
Second hand case-2
Compared with "subsequent case-1", perform sleep operation in the while loop and catch exceptions with try catch
Guess: print "hello interrupt" six times, and print "* * * program end"
Operation results:
An exception is reported and the program cannot be stopped
Cause of problem:
When thread t1 is sleeping, thread t2 sets t1 Interrupt, resulting in an exception
After an exception is reported, the interrupt state is reset to false and cannot be stopped
Solution:
Set interrupt once in try catch
Second hand case-3
conclusion
Static method thread interrupted
Code demonstration
Method description
Interrupted vs IsInterrupted
Thread interrupt summary
Understand the system architecture design
LockSupport
What is LockSupport
LockSupport is the basic thread blocking primitive used to create locks and other synchronization classes.
Brief description
One sentence to explain LockSupport:
LockSupport is an enhanced version of the thread wait / notify mechanism
park() and unpark() in LockSupport are used to block threads and unblock threads respectively.
In short, it is stronger than wait/notify and await/signal.
Three ways to make threads wait and wake up
-
Method 1: use the wait() method in object to make the thread wait, and use the notify() method in object to wake up the thread
-
Method 2: use the await() method of Condition in the JUC package to make the thread wait, and use the signal() method to wake up the thread
-
Method 3: LockSupport class can block the current thread and wake up the specified blocked thread
Two important methods
Thread waiting wake-up mechanism
Wait and Notify restrictions
The wait and notify methods in the Object class implement thread waiting and wake-up
public class WaitNotifyDemo { static Object lock = new Object(); public static void main(String[] args) { new Thread(()->{ synchronized (lock) { System.out.println(Thread.currentThread().getName()+" come in."); try { lock.wait(); } catch (Exception e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+" Wake up."); }, "Thread A").start(); new Thread(()->{ synchronized (lock) { lock.notify(); System.out.println(Thread.currentThread().getName()+" notice."); } }, "Thread B").start(); } }
The wait and notify methods must be used in the synchronization block or method in pairs, otherwise they will throw Java lang.IllegalMonitorStateException.
public class WaitNotifyDemo { static Object lock = new Object(); public static void main(String[] args) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " come in."); try { lock.wait(); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " Wake up."); }, "Thread A").start(); new Thread(() -> { lock.notify(); System.out.println(Thread.currentThread().getName() + " notice."); }, "Thread B").start(); } }
Exception in thread "Thread B" java.lang.IllegalMonitorStateException at java.base/java.lang.Object.notify(Native Method) at com.example.demo07.main.WaitNotifyDemo.lambda$main$1(WaitNotifyDemo.java:19) at java.base/java.lang.Thread.run(Thread.java:829) Thread A come in. Thread A Wake up. java.lang.IllegalMonitorStateException at java.base/java.lang.Object.wait(Native Method) at java.base/java.lang.Object.wait(Object.java:328) at com.example.demo07.main.WaitNotifyDemo.lambda$main$0(WaitNotifyDemo.java:11) at java.base/java.lang.Thread.run(Thread.java:829)
The call sequence should wait before notify before OK.
wait first and then notify and notifyall methods. The waiting thread will be awakened, otherwise it cannot be awakened
public class WaitNotifyDemo { static Object lock = new Object(); public static void main(String[] args) { new Thread(()->{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock) { System.out.println(Thread.currentThread().getName()+" come in."); try { lock.wait(); } catch (Exception e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+" Wake up."); }, "Thread A").start(); new Thread(()->{ synchronized (lock) { lock.notify(); System.out.println(Thread.currentThread().getName()+" notice."); } }, "Thread B").start(); } }
Thread B notice. Thread A come in. .......Infinite waiting......
Wait and Signal restrictions
The signal method after await in the Condition interface implements thread waiting and wake-up, which is similar to the wait and notify methods in the Object class.
public class ConditionAwaitSignalDemo { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+" come in."); lock.lock(); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName()+" Wake up."); },"Thread A").start(); new Thread(()->{ try { lock.lock(); condition.signal(); System.out.println(Thread.currentThread().getName()+" notice."); }finally { lock.unlock(); } },"Thread B").start(); } }
Output results:
Thread A come in. Thread B notice. Thread A Wake up.
await and signal methods must be used in a synchronization block or method in pairs, or Java. Net will be thrown lang.IllegalMonitorStateException.
The call sequence should be await before signal is OK.
Introduction to LockSupport method
LockSupport method summary in API
Traditional synchronized and Lock implement the constraint of waiting for wake-up notification
- To obtain and hold a lock, a thread must be in a lock block (synchronized or lock)
- You must wait before waking up before the thread can be awakened
park wait and unpark wake in LockSupport class
Basic thread blocking primitives for creating locks and other synchronization classes.
This class associates, with each thread that uses it, a permit (in the sense of the Semaphore class). A call to park will return immediately if the permit is available, consuming it in the process; otherwise it may block. A call to unpark makes the permit available, if it was not already available. (Unlike with Semaphores though, permits do not accumulate. There is at most one.) link
LockSupport is the basic thread blocking primitive used to create locks and other synchronization classes.
LockSupport class uses a concept called permission to block and wake up threads. Each thread has a permission. Permission has only two values: 1 and zero. The default is zero.
You can think of a license as a (0.1) Semaphore, but unlike Semaphore, the cumulative upper limit of the license is 1.
The operation of blocking and waking up threads is realized through park() and unpark(thread) methods
park()/park(Object blocker) - block the current thread and block the incoming specific thread
public class LockSupport { ... public static void park() { UNSAFE.park(false, 0L); } public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); } ... }
The default permission is 0, so when you call the park() method at the beginning, the current thread will block. When another thread sets the permission of the current thread to 1, the park method will wake up, and then set the permission to 0 again and return.
unpark(Thread thread) - wakes up the specified thread that is blocked
public class LockSupport { ... public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); } ... }
After the unpark(thread) method is called, the permission permission of the thread will be set to 1 (note that if the unpark method is called multiple times, it will not accumulate, and the pemit value is still 1) and the thead thread will wake up automatically, that is, locksupport in the previous blocking The park () method returns immediately.
LockSupport case analysis
Normal use
public class LockSupportDemo2 { public static void main(String[] args) { Thread a = new Thread(()->{ System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis()); LockSupport.park(); System.out.println(Thread.currentThread().getName() + " Wake up. " + System.currentTimeMillis()); }, "Thread A"); a.start(); Thread b = new Thread(()->{ LockSupport.unpark(a); System.out.println(Thread.currentThread().getName()+" notice."); }, "Thread B"); b.start(); } }
Output results:
Thread A come in. Thread B notice. Thread A Wake up.
Normal + no lock block requirements.
LockSupport can ignore the previously incorrect wake first and wait sequence.
[support] first release the lock, unpark, and then add the lock
Why can I wake up the thread first and then block the thread?
Because unpark obtains a voucher and then calls the park method, you can consume the voucher in a proper way, so it will not be blocked.
public class LockSupportDemo2 { public static void main(String[] args) { Thread a = new Thread(()->{ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis()); LockSupport.park(); System.out.println(Thread.currentThread().getName() + " Wake up. " + System.currentTimeMillis()); }, "Thread A"); a.start(); Thread b = new Thread(()->{ LockSupport.unpark(a); System.out.println(Thread.currentThread().getName()+" notice."); }, "Thread B"); b.start(); } }
[support] unpack multiple times
public class LockSupportDemo2 { public static void main(String[] args) { Thread a = new Thread(()->{ System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis()); LockSupport.park(); System.out.println(Thread.currentThread().getName() + " Wake up. " + System.currentTimeMillis()); }, "Thread A"); a.start(); Thread b = new Thread(()->{ LockSupport.unpark(a); LockSupport.unpark(a); LockSupport.unpark(a); System.out.println(Thread.currentThread().getName()+" notice."); }, "Thread B"); b.start(); } }
[not supported] pack multiple times, resulting in infinite waiting
public class LockSupportDemo2 { public static void main(String[] args) { Thread a = new Thread(()->{ System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis()); LockSupport.park(); LockSupport.park(); System.out.println(Thread.currentThread().getName() + " Wake up. " + System.currentTimeMillis()); }, "Thread A"); a.start(); Thread b = new Thread(()->{ LockSupport.unpark(a); System.out.println(Thread.currentThread().getName()+" notice."); }, "Thread B"); b.start(); } }
[support] multiple passes provided by different threads
package com.juc; import java.util.concurrent.locks.LockSupport; public class LockSupportDemo2 { public static void main(String[] args) { //park multiple times Thread thread1 = new Thread(()->{ System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis()); LockSupport.park(); LockSupport.park(); LockSupport.park(); System.out.println(Thread.currentThread().getName() + " awaken. " + System.currentTimeMillis()); }, "Thread A"); thread1.start(); //Thread B unpark once new Thread(()->{ LockSupport.unpark(thread1); System.out.println(Thread.currentThread().getName()+" notice."); }, "Thread B").start(); //Thread C unpark once new Thread(()->{ LockSupport.unpark(thread1); System.out.println(Thread.currentThread().getName()+" notice."); }, "Thread C").start(); //Thread D unpark once new Thread(()->{ LockSupport.unpark(thread1); System.out.println(Thread.currentThread().getName()+" notice."); }, "Thread D").start(); } }
Key notes
LockSupport is the basic thread blocking primitive used to create lock and other synchronization classes.
LockSuport is a thread blocking tool class. All methods are static methods. Threads can be blocked at any position. After blocking, there are also corresponding wake-up methods. In the final analysis, LockSupport calls the native code in Unsafe.
LockSupport provides park() and unpark() methods to block threads and unblock threads
LockSupport has a permission association with each thread that uses it. permit is equivalent to the switch of 1 and 0. The default is 0,
When you call unpark once, add 1 to become 1,
Calling park once will consume permit, that is, change 1 to 0, and park will return immediately.
If you call park again, it will become blocked (because if the permission is zero, it will be blocked here until the permission becomes 1). At this time, calling unpark will set the permission to 1. Each thread has a related permission. There is only one permission at most. Repeated calls to unpark will not accumulate credentials.
Image understanding
Thread blocking requires the consumption of a permit, which is only 1 at most.
When the park method is called
- If there is a voucher, it will be consumed directly and then exit normally.
- If there is no certificate, you must block and wait for the certificate to be available.
On the contrary, unpark will add one voucher, but there can only be one voucher at most, which is accumulated without release.
Interview questions
1. Why can I wake up the thread first and then block the thread?
Because unpark obtains a voucher and then calls the park method, you can consume the voucher in a proper way, so it will not be blocked.
2. Why block twice after waking up twice, but the end result will block the thread?
Because the maximum number of vouchers is 1 (cannot be accumulated), calling unpark twice in a row has the same effect as calling unpark once, and only one voucher will be added; However, calling park twice needs to consume two vouchers, which are not enough and cannot be released.
Java memory model value JMM
Interview question feedback
Computer hardware architecture
What is JMM
Three features under JVM specification
visibility
Atomicity
It means that an operation is non interruptible, that is, in a multithreaded environment, the operation cannot be disturbed by other threads
Order
Multi thread traversal reading process
Read process
Small summary
All shared variables we define are stored in physical main memory
Each thread has its own independent working memory, which stores a copy of the variable used by the thread (a copy of the variable in the main memory)
All operations of a thread on shared variables must be carried out in the thread's own working memory first and then written back to the main memory. You can't read or write directly from the main memory (you can't skip levels)
Variables in the working memory of other threads cannot be accessed directly between different threads. Variable values between threads need to be transferred through the main memory (peers cannot access each other)
Happens before principle
x. y case description
Description of prior occurrence principle
Happens before eight rules
Code description
Memory barrier
volatile memory semantics
What is a memory barrier
Memory barrier (also known as memory barrier, memory barrier, barrier instruction, etc.) is a kind of synchronization barrier instruction, which is a synchronization point in the operation of random access to memory by CPU or compiler, so that all read and write operations before this point can be executed before the operation after this point can be started), so as to avoid code reordering. Memory barrier is actually a kind of JVM instruction. The rearrangement rules of Java memory model will require the Java compiler to insert specific memory barrier instructions when generating JVM instructions. Through these memory barrier instructions, volatile realizes the visibility and order in Java memory model, but volatile cannot guarantee atomicity.
All write operations before the memory barrier must be written back to the main memory,
All read operations after the memory barrier can obtain the latest results of all write operations before the memory barrier (visibility is achieved).
Source code analysis of four types of memory barriers
What do the four barriers mean
Memory barrier insertion strategy
volatile variable rule of happens before
Write a summary
1. Insert a StoreStore barrier in front of each volatile write operation
2. Insert a StoreLoad barrier after each volatile write operation
Reading summary
3. Insert a LoadLoad barrier after each volatile read operation
4. Insert a LoadStore barrier after each volatile read operation
volatile keyword
Ensure visibility
Code description
Without volatile and visibility, the program cannot stop
volatile is added to ensure visibility, and the program can stop
Reading and writing process of volatile variable
- Lock (lock) acts on variables in main memory and identifies variables as thread exclusive state.
- Read is a variable that acts on the main memory and transfers the value of the variable from the main memory to the working memory of the thread for the next load operation.
- Load, the variable that acts on the working memory. Put the read operation main memory variable into the variable copy of the working memory.
- Use (use), a variable that acts on the working memory and transfers the variable in the working memory to the execution engine. This operation will be executed whenever the virtual machine encounters a bytecode instruction that needs to use the value of the variable.
- Assign is a variable that acts on the working memory. It assigns a value received from the execution engine to the variable copy of the working memory. This operation will be executed whenever the virtual machine encounters a bytecode instruction that assigns a value to the variable.
- Store, a variable that acts on the working memory. It transfers the value of a variable from the working memory to the main memory for subsequent write.
- Write: acts on a variable in main memory. It puts the value of the variable obtained by the store operation from working memory into the variable in main memory.
- Unlock: a variable that acts on the main memory. It releases a locked variable, and the released variable can be locked by other threads.
No atomicity
Code demonstration
Output result: basically, it will be less than 10000, and 10000 may occur once
Bytecode angle description
Atomicity is not guaranteed
Reading a common variable
volatile processing of instructions
Read and assign a volatile variable
Different non atomic operations during the gap period
Prohibit instruction rearrangement
Description and case
Memory barrier secondary review
The prohibition of instruction rearrangement related to volatile
Code description
Use volatile correctly
Use case: status flag
Use case: read more and write less
Use case: two terminal retrieval
Problem code
Look at the problem code under single thread and multi thread
Double ended retrieval of correct code
The static inner class implements the singleton pattern
volatile summary
What is the memory barrier
Memory barrier:
Is a barrier instruction that causes the CPU or compiler to perform a sort constraint on memory operations issued before and after the barrier instruction. Also called memory fence or fence instruction
Memory barrier four instructions
Insert a StoreStore barrier before each volatile write operation
Insert a StoreLoad barrier after each volatile write operation
Insert a LoadLoad barrier after each volatile read operation
Insert a LoadStore barrier after each volatile read operation
volatile keyword system bottom layer
Bytecode level
keyword
volatile visibility
volatile forbidden rearrangement
Write instruction
Read instruction
Compare Lock's understanding
One sentence summary
CAS
Before CSA
Ensuring thread safety in multithreading environment
What is CAS
Description and principle
Hardware level assurance
CASDemo code
compareAndSet source code
Atomic reference
Handwritten spin lock
Handwritten spin lock
What is spin lock
ABA problem
Disadvantages of CAS: CPU idling
ABA problem
Code: ABA problem
Code: solve ABA problem
Postmarked atomic reference: AtomicStampedReference
atomicStampedReference.compareAndSet(x,y,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
use:
Atomic operation class
What's the difference between red and black?
Why use LongAdder instead of AtomicLong?
Basic type atomic class
countDownLatch, where do you use it?
class MyNumber{ @Getter private AtomicInteger atomicInteger = new AtomicInteger(); public void addPlusPlus(){ atomicInteger.incrementAndGet(); } } public class AtomicIntegerDemo{ public static void main(String[] args) throws InterruptedException{ MyNumber myNumber = new MyNumber(); CountDownLatch countDownLatch = new CountDownLatch(100); for (int i = 1; i <=100; i++) { new Thread(() -> { try{ for (int j = 1; j <=5000; j++){ myNumber.addPlusPlus(); } }finally { countDownLatch.countDown(); } },String.valueOf(i)).start(); } countDownLatch.await();//Without this line of code, because the main thread is too fast, at the end of main, atomicinteger Incrementandget() hasn't reached 500000 yet System.out.println(myNumber.getAtomicInteger().get()); } }
Array type atomic class
public class AtomicIntegerArrayDemo{ public static void main(String[] args){ AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]); //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5); //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5}); for (int i = 0; i <atomicIntegerArray.length(); i++) { System.out.println(atomicIntegerArray.get(i)); } System.out.println(); System.out.println(); System.out.println(); int tmpInt = 0; tmpInt = atomicIntegerArray.getAndSet(0,1122); System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0)); atomicIntegerArray.getAndIncrement(1); atomicIntegerArray.getAndIncrement(1); tmpInt = atomicIntegerArray.getAndIncrement(1); System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1)); } }
Reference type atomic class
AtomicReference
@Getter @ToString @AllArgsConstructor class User{ String userName; int age; } public class AtomicReferenceDemo{ public static void main(String[] args){ User z3 = new User("z3",24); User li4 = new User("li4",26); AtomicReference<User> atomicReferenceUser = new AtomicReference<>(); atomicReferenceUser.set(z3); System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString()); System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString()); } }
Handwritten spin lock
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 * Title: implementing a spin lock * Spin lock benefits: loop comparison acquisition has no blocking like wait. * * The spin lock is completed through CAS operation. Thread A first calls the myLock method and holds the lock for 5 seconds. Then, thread B comes in and finds out * Currently, A thread holds A lock, which is not null, so it can only wait by spin until A releases the lock and 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(); //Pause the thread for a moment try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); } spinLockDemo.myUnLock(); },"A").start(); //Pause the thread for A while to ensure that thread A starts and completes before thread B try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { spinLockDemo.myLock(); spinLockDemo.myUnLock(); },"B").start(); } }
AtomicStampedReference
Solving ABA problems
public class ABADemo { static AtomicInteger atomicInteger = new AtomicInteger(100); static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1); public static void main(String[] args) { abaProblem(); abaResolve(); } public static void abaResolve() { new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println("t3 ----1st time stamp "+stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100,101,stamp,stamp+1); System.out.println("t3 ----2nd time stamp "+atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println("t3 ----3rd time stamp "+atomicStampedReference.getStamp()); },"t3").start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println("t4 ----1st time stamp "+stamp); //Pause the thread for a few seconds try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference()); },"t4").start(); } public static void abaProblem() { new Thread(() -> { atomicInteger.compareAndSet(100,101); atomicInteger.compareAndSet(101,100); },"t1").start(); try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { atomicInteger.compareAndSet(100,20210308); System.out.println(atomicInteger.get()); },"t2").start(); } }
AtomicMarkableReference
It is not recommended to solve ABA problems
It is used to solve the one-time problem and is not suitable for reuse
package com.atguigu.juc.senior.inner.atomic; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicMarkableReference; import java.util.concurrent.atomic.AtomicStampedReference; /** * @auther zzyy * @create 2020-05-23 10:56 */ public class ABADemo { static AtomicInteger atomicInteger = new AtomicInteger(100); static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1); static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false); public static void main(String[] args) { new Thread(() -> { atomicInteger.compareAndSet(100,101); atomicInteger.compareAndSet(101,100); System.out.println(Thread.currentThread().getName()+"\t"+"update ok"); },"t1").start(); new Thread(() -> { //Pause the thread for a few seconds try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicInteger.compareAndSet(100,2020); },"t2").start(); //Pause the thread for a few seconds try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicInteger.get()); System.out.println(); System.out.println(); System.out.println(); System.out.println("============Here is ABA Problem solving,Let's know how many times the reference variable has been changed in the middle========================="); new Thread(() -> { System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+stampedReference.getStamp()); //Deliberately pause for 200 milliseconds to let the t4 thread get the same version number as t3 try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 2 Minor version number"+stampedReference.getStamp()); stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 3 Minor version number"+stampedReference.getStamp()); },"t3").start(); new Thread(() -> { int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t =======1 Minor version number"+stamp); //Pause for 2 seconds and let t3 complete the ABA operation first to see if you can modify it try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } boolean b = stampedReference.compareAndSet(100, 2020, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"\t=======2 Minor version number"+stampedReference.getStamp()+"\t"+stampedReference.getReference()); },"t4").start(); System.out.println(); System.out.println(); System.out.println(); System.out.println("============AtomicMarkableReference I don't care how many times the reference variable has been changed, but only whether it has been changed======================"); new Thread(() -> { boolean marked = markableReference.isMarked(); System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+marked); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } markableReference.compareAndSet(100,101,marked,!marked); System.out.println(Thread.currentThread().getName()+"\t 2 Minor version number"+markableReference.isMarked()); markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked()); System.out.println(Thread.currentThread().getName()+"\t 3 Minor version number"+markableReference.isMarked()); },"t5").start(); new Thread(() -> { boolean marked = markableReference.isMarked(); System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+marked); //Pause the thread for a few seconds try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } markableReference.compareAndSet(100,2020,marked,!marked); System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked()); },"t6").start(); } }
Object to modify the atomic class
Manipulate certain fields within a non thread safe object in a thread safe manner
AtomicIntegerFieldUpdater
class BankAccount{ private String bankName = "CCB";//bank public volatile int money = 0;//Amount of money AtomicIntegerFieldUpdater<BankAccount> accountAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money"); //No lock + high performance, local minimally invasive public void transferMoney(BankAccount bankAccount){ accountAtomicIntegerFieldUpdater.incrementAndGet(bankAccount); } } /** * @auther zzyy * @create 2020-07-14 18:06 * Manipulate certain fields of non thread safe objects in a thread safe manner. * Requirements: * 1000 If an individual transfers one yuan to an account at the same time, the cumulative amount should be increased by 1000 yuan, * In addition to synchronized and CAS, it can also be implemented using AtomicIntegerFieldUpdater. */ public class AtomicIntegerFieldUpdaterDemo{ public static void main(String[] args){ BankAccount bankAccount = new BankAccount(); for (int i = 1; i <=1000; i++) { int finalI = i; new Thread(() -> { bankAccount.transferMoney(bankAccount); },String.valueOf(i)).start(); } //Pause milliseconds try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(bankAccount.money); } }
AtomicReferenceField
class MyVar{ public volatile Boolean isInit = Boolean.FALSE; AtomicReferenceFieldUpdater<MyVar,Boolean> atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit"); public void init(MyVar myVar){ if(atomicReferenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE)){ System.out.println(Thread.currentThread().getName()+"\t"+"---init....."); //Pause the thread for a few seconds try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t"+"---init.....over"); }else{ System.out.println(Thread.currentThread().getName()+"\t"+"------Another thread is initializing"); } } } /** * @auther zzyy * @create 2021-03-18 17:20 * Multithreading calls the initialization method of a class concurrently. If it has not been initialized, it will perform initialization. It is required that it can only be initialized once */ public class AtomicIntegerFieldUpdaterDemo{ public static void main(String[] args) throws InterruptedException{ MyVar myVar = new MyVar(); for (int i = 1; i <=5; i++) { new Thread(() -> { myVar.init(myVar); },String.valueOf(i)).start(); } } }
Deep analysis of atomic operation enhancement principle
LongAdder
Basic use
Ali is dying
Common API
Use case
The LongAccumulator provides custom function operations
A long type aggregator needs to pass in a long type binary operation, which can be used to calculate various aggregation operations, including addition and multiplication
public class LongAccumulatorDemo { LongAdder longAdder = new LongAdder(); public void add_LongAdder() { longAdder.increment(); } //LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y,0); LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator() { @Override public long applyAsLong(long left, long right) { return left - right; } },777); public void add_LongAccumulator() { longAccumulator.accumulate(1); } public static void main(String[] args) { LongAccumulatorDemo demo = new LongAccumulatorDemo(); demo.add_LongAccumulator(); demo.add_LongAccumulator(); System.out.println(demo.longAccumulator.longValue()); } }
LongAdderAPIDemo
package com.atguigu.juc.atomics; import java.util.concurrent.atomic.LongAccumulator; import java.util.concurrent.atomic.LongAdder; /** * @auther zzyy * @create 2021-03-19 15:59 */ public class LongAdderAPIDemo { public static void main(String[] args) { LongAdder longAdder = new LongAdder(); longAdder.increment(); longAdder.increment(); longAdder.increment(); System.out.println(longAdder.longValue()); LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x * y,2); longAccumulator.accumulate(1); longAccumulator.accumulate(2); longAccumulator.accumulate(3); System.out.println(longAccumulator.longValue()); } }
performance testing
1. Define five methods
2. Define test method
3. Test results
Why is LongAdder fast
LongAdder source code analysis
It's too difficult. I haven't seen it yet. I'll wait for the later supplement [December 31, 2021]
Unsafe class
What is the UnSafe class
Is atomic shaping safe
getAndIncrement source code
Bottom assembly
ThreadLocal
Introduction to Threadlocal
What is / can do / API
ThreadLocal provides thread local variables. These variables are different from normal variables, because each thread has its own independently initialized variable copy when accessing the ThreadLocal instance (through its get or set method).
A ThreadLocal instance is usually a private static field in a class that is used to associate a state (for example, user ID or transaction ID) with a thread.
Each thread has its own copy of local variables (it doesn't bother others to use its own variables and doesn't share them with others. Everyone has a copy and everyone has a copy),
It mainly solves the problem of allowing each thread to bind its own value. By using the get() and set() methods, it can obtain the default value or change its value to the value of the copy saved by the current thread, so as to avoid the thread safety problem.
Case: house purchase commission
ThreadLocal initialization method
Housing Commission case
Existing problems
The custom ThreadLocal variable must be recycled. Especially in the thread pool scenario, threads are often reused,
If the custom ThreadLocal variable is not cleared, subsequent business logic may be affected and memory leakage may be caused.
Try to recycle with try finally blocks in the proxy.
Start with ALI ThreadLocal specification
Non thread safe SimpleDateFormat
Official documents
Error code demonstration
Problems arising
Source code analysis conclusion
Solution
Option 1:
for (int i = 1; i <=30; i++) { new Thread(() -> { try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//Every time you use new System.out.println(sdf.parse("2020-11-11 11:11:11")); sdf = null;//Help GC recycle } catch (Exception e) { e.printStackTrace(); } },String.valueOf(i)).start(); }
Option 2:
code
private static final ThreadLocal<SimpleDateFormat> sdf_threadLocal = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
This solution is recommended by Ali
Other options:
code
DataUtils
package com.atguigu.juc.senior.utils; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; /** * @auther zzyy * @create 2020-05-03 10:14 */ public class DateUtils { /* 1 SimpleDateFormat If multithreading sharing is a thread unsafe class public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String format(Date date) { return SIMPLE_DATE_FORMAT.format(date); } public static Date parse(String datetime) throws ParseException { return SIMPLE_DATE_FORMAT.parse(datetime); }*/ //2 ThreadLocal can ensure that each thread can get its own SimpleDateFormat object, so there will be no competition. public static final ThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); public static String format(Date date) { return SIMPLE_DATE_FORMAT_THREAD_LOCAL.get().format(date); } public static Date parse(String datetime) throws ParseException { return SIMPLE_DATE_FORMAT_THREAD_LOCAL.get().parse(datetime); } //3 DateTimeFormatter replaces SimpleDateFormat /*public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static String format(LocalDateTime localDateTime) { return DATE_TIME_FORMAT.format(localDateTime); } public static LocalDateTime parse(String dateString) { return LocalDateTime.parse(dateString,DATE_TIME_FORMAT); }*/ }
homework
From: Ali manual
//3 DateTimeFormatter replaces SimpleDateFormat /*public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static String format(LocalDateTime localDateTime) { return DATE_TIME_FORMAT.format(localDateTime); } public static LocalDateTime parse(String dateString) { return LocalDateTime.parse(dateString,DATE_TIME_FORMAT); }*/
ThreadLocal source code analysis
ThreadLocalMap
threadLocalMap is actually an Entry object with threadLocal instance as key and any object as value.
When we assign a value to the threadLocal variable, we actually store the Entry with the current threadLocal instance as the key and value into the threadLocalMap
ThreadLocal memory leak
Existing problems:
In the thread pool environment, threads are often reused
What is a memory leak
The memory occupied by objects or variables that will no longer be used cannot be recycled, which is a memory leak.
Look at ThreadLocalMap again
ThreadLocalMap literally shows that this is a map that stores ThreadLocal objects (in fact, it is used as the Key), but ThreadLocal objects are packaged in two layers:
(1) The first layer of packaging uses WeakReference < threadloca <? > > Change the ThreadLocal object into a weakly referenced object;
(2) The second layer of packaging defines a special class Entry to extend WeakReference < ThreadLocal <? > >
Overall architecture
Four references of strong, weak and virtual
When the memory is insufficient, the JVM starts garbage collection. For strongly referenced objects, even if there is an OOM, the object will not be recycled and will not be collected.
Strong reference is our most common common object reference. As long as there is a strong reference pointing to an object, it can indicate that the object is still "alive", and the garbage collector will not touch this object. The most common in Java is strong reference. Assigning an object to a reference variable is a strong reference. When an object is referenced by a strongly referenced variable, it is in reachable state. It cannot be recycled by the garbage collection mechanism. Even if the object will never be used in the future, the JVM will not recycle it. Therefore, strong references are one of the main causes of JAVA memory leaks.
For an ordinary object, if there is no other reference relationship, as long as the scope of the reference is exceeded or the corresponding (strong) reference is explicitly assigned null,
It is generally considered that it can be garbage collected (of course, the specific recycling time depends on the garbage collection strategy).
public static void strongReference(){ MyObject myObject = new MyObject(); System.out.println("-----gc before: "+myObject); myObject = null; System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after: "+myObject); }
Soft reference is a relatively strong reference, which weakens some references and needs to be used in Java Implemented by lang.ref.softreference class, the object can be exempted from some garbage collection.
For objects with only soft references,
When the system memory is sufficient, it will not be recycled,
When the system runs out of memory, it is recycled.
Soft references are usually used in memory sensitive programs. For example, the cache is useful for soft references. When the memory is enough, they are retained and recycled if not enough!
class MyObject{ //You don't need to call this method in general development. This time is just for lecture demonstration @Override protected void finalize() throws Throwable{ System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked...."); } } /** * @auther zzyy * @create 2021-03-24 10:31 */ public class ReferenceDemo{ public static void main(String[] args){ //When our memory is not enough, soft will be recycled. Set our memory size: - Xms10m -Xmx10m SoftReference<MyObject> softReference = new SoftReference<>(new MyObject()); System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after Enough memory: "+softReference.get()); try { byte[] bytes = new byte[9 * 1024 * 1024]; }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("-----gc after insufficient memory: "+softReference.get()); } } public static void strongReference(){ MyObject myObject = new MyObject(); System.out.println("-----gc before: "+myObject); myObject = null; System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after: "+myObject); } }
Weak references require Java Lang.ref.weakreference class, which has a shorter lifetime than soft reference,
For objects with only weak references, as soon as the garbage collection mechanism runs, the memory occupied by the object will be reclaimed regardless of whether the memory space of the JVM is sufficient.
class MyObject { //You don't need to call this method in general development. This time is just for lecture demonstration @Override protected void finalize() throws Throwable { System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked...."); } } /** * @auther zzyy * @create 2021-03-24 10:31 */ public class ReferenceDemo { public static void main(String[] args) { WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject()); System.out.println("-----gc before Enough memory: "+weakReference.get()); System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after Enough memory: "+weakReference.get()); } public static void softReference() { //When our memory is not enough, soft will be recycled. Set our memory size: - Xms10m -Xmx10m SoftReference<MyObject> softReference = new SoftReference<>(new MyObject()); System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after Enough memory: "+softReference.get()); try { byte[] bytes = new byte[9 * 1024 * 1024]; }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("-----gc after insufficient memory: "+softReference.get()); } } public static void strongReference() { MyObject myObject = new MyObject(); System.out.println("-----gc before: "+myObject); myObject = null; System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after: "+myObject); } }
Virtual reference requires Java Lang.ref.phantomreference class.
As the name suggests, it is virtual. Unlike other references, virtual references do not determine the life cycle of objects.
If an object holds only virtual references, it may be recycled by the garbage collector at any time as if it had no references,
It cannot be used alone or access objects through it. Virtual references must be used in conjunction with reference queues.
The main function of virtual reference is to track the state of the object being garbage collected. It only provides a mechanism to ensure that the object does something after it is finalize d. The get method of phantom reference always returns null, so the corresponding reference object cannot be accessed.
Its significance is that an object has entered the finalization stage and can be recycled by gc to realize more flexible recycling operations than the finalization mechanism.
In other words, the only purpose of setting virtual reference association is to receive a system notification or add further processing when the object is recycled by the collector.
class MyObject { //You don't need to call this method in general development. This time is just for lecture demonstration @Override protected void finalize() throws Throwable { System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked...."); } } /** * @auther zzyy * @create 2021-03-24 10:31 */ public class ReferenceDemo { public static void main(String[] args) { ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue(); PhantomReference<MyObject> phantomReference = new PhantomReference<>(new MyObject(),referenceQueue); //System.out.println(phantomReference.get()); List<byte[]> list = new ArrayList<>(); new Thread(() -> { while (true) { list.add(new byte[1 * 1024 * 1024]); try { TimeUnit.MILLISECONDS.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(phantomReference.get()); } },"t1").start(); new Thread(() -> { while (true) { Reference<? extends MyObject> reference = referenceQueue.poll(); if (reference != null) { System.out.println("***********A virtual object has been added to the queue"); } } },"t2").start(); //Pause the thread for a few seconds try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } public static void weakReference() { WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject()); System.out.println("-----gc before Enough memory: "+weakReference.get()); System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after Enough memory: "+weakReference.get()); } public static void softReference() { //When our memory is not enough, soft will be recycled. Set our memory size: - Xms10m -Xmx10m SoftReference<MyObject> softReference = new SoftReference<>(new MyObject()); System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after Enough memory: "+softReference.get()); try { byte[] bytes = new byte[9 * 1024 * 1024]; }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("-----gc after insufficient memory: "+softReference.get()); } } public static void strongReference() { MyObject myObject = new MyObject(); System.out.println("-----gc before: "+myObject); myObject = null; System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----gc after: "+myObject); } }
Why use weak references
Conclusion:
From the previous set, getentry and remove methods, we can see that in the life cycle of threadLocal, for the problem of memory leakage in threadLocal,
Clean up the dirty entry with null key through three methods: expungeStaleEntry, cleansomeslots and replacestateentry.
Weak citation conclusion
Best practices
Java object memory layout and object header
Interview question feedback
Object object = new Object()
Talk about your understanding of this sentence? Generally speaking, by default, how much memory space does a new object occupy in JDK8
Object's memory layout
In the HotSpot virtual machine, the storage layout of objects in heap memory can be divided into three parts:
An object instance: object header, instance data, and Padding
Object header
Object header = object tag (MarkWord) + class meta information (type pointer)
MarkWord is designed as a non fixed data structure
In order to store as much data as possible in a very small memory space. It will reuse its own storage space according to the state of the object, that is, the data stored in MarkWord will change with the change of lock flag bit during operation.
Default storage: HashCode, generation age, lock flag bit and other information of the object.
Object tag (MarkWord)
What does it hold?
The HashCode, generation age, lock flag bit and other information of the object are stored by default.
This information is independent of the definition of the object itself, so MarkWord is designed as a non fixed data structure
In order to store as much data as possible in a very small memory space. It will reuse its own storage space according to the state of the object, that is, the data stored in MarkWord will change with the change of lock flag bit during operation.
Class meta information (type pointer)
Object points to its class metadata. The virtual machine uses this pointer to determine which class instance this object is.
Refer to the original drawing in the JVM course of Mr. Song Hongkang
How big is the object header
In the 64 bit system, Mark Word occupies 8 bytes and type pointer occupies 8 bytes, a total of 16 bytes.
Instance data
Store the attribute (Field) data information of the class, including the attribute information of the parent class,
If the instance part of the array also includes the length of the array, this part of memory is aligned by 4 bytes.
class MyObject{ int i=5; char a='a'; } MyObjec Object first 16 bytes: MarkWord 8 Bytes + Type pointer 8 bytes int Type: 32 bit 3 Bytes char Type: 16 bit 2 Bytes Total: 16+3+2=21 Bytes Attribute padding: 3 bytes Total: 24 bytes
Align fill
The virtual machine requires that the starting address of the object must be an integer multiple of 8 bytes.
The filling data does not have to exist, but only for byte alignment. This part of memory is supplemented and aligned by 8 bytes.
MarkWord object header
Official website theory
Again, object header MarkWord
32-bit virtual machine
64 bit virtual machine
64 bit virtual machine source code
Object layout description and compressed pointer
JOL use case
new Object code description
Try another object
Modify GC age
The GC age is stored in 4 bit s, with a maximum value of 15. When the parameter modification is greater than 15, an error will be reported and the JVM cannot be started
Automatic compression
Auto compression is on by default
Print JVM parameters using
Turn off automatic compression
Synchronized lock upgrade
Introduction of interview questions
[forced] when the concurrency is high, the performance loss of lock should be considered for synchronous call. If you can use a lock free data structure, do not use a lock; If you can lock blocks, don't lock the whole method body; If you can use object locks, don't use class locks.
Note: as far as possible, the workload of locked block is as small as possible, avoiding calling the RPC method in the lock block.
Synchronized performance changes
Why can every object become a lock
Monitor (monitor lock)
Synchronized lock optimization background
User mode and kernel mode
Lock upgrade process
The lock used for synchronized is stored in Mark Word in the Java object header. The lock upgrade function mainly depends on the lock flag bit and release bias lock flag bit in Mark Word
Lock upgrade details
No lock
Lockless case
hashcode is stored only when it is called
hashcode in hexadecimal
hashcode in binary
Conclusion: the program will not have lock competition
Bias lock
What is a bias lock:
When a piece of synchronization code has been accessed by the same thread many times, because there is only one thread, the thread will automatically obtain the lock during subsequent access
Why is there a bias lock:
After research, the author of Hotspot found that in most cases: in the case of multithreading, the lock not only does not have multithreading competition, but also has the situation that the lock is obtained by the same thread many times. Biased lock appears in this case. Its emergence is to improve the performance only when one thread performs synchronization.
Process Description:
Then you only need to record the biased thread ID when the lock is owned for the first time. In this way, the biased thread always holds the lock (when the subsequent thread enters and exits this code block with synchronization lock, it does not need to add and release the lock again. Instead, it directly compares whether the biased lock pointing to the current thread is stored in the object header).
If equality means that the biased lock is biased towards the current thread, there is no need to try to obtain the lock until the competition occurs. After each synchronization, check whether the biased thread ID of the lock is consistent with the current thread ID. if it is consistent, enter the synchronization directly. There is no need to update the object header every time the lock is unlocked. If there is only one thread that uses locks from beginning to end, it is obvious that locking bias has little additional overhead and high performance.
Upgrade to lightweight lock:
If inconsistency means competition, the lock is not always biased to the same thread. At this time, it may need to be upgraded to a lightweight lock to ensure fair competition between threads.
Biased lock the thread holding the biased lock will release the lock only when other threads try to compete for the biased lock. The thread will not actively release the biased lock.
Cancellation of bias lock
If the competing thread fails to update the object header, it will wait for the global security point (no code will be executed at this time) to revoke the bias lock.
Main role
64 bit mark graph
Holding instructions of bias lock
Example of bias lock
Lock JVM command
Demonstration of bias lock code [no effect]
Demonstration of bias lock code [ effective ]
Cancellation of bias lock
Partial lock acquisition and revocation process
Lightweight Locking
64 bit marker map
Lightweight lock acquisition
Code demonstration
Spin to a certain number of times
The difference between lightweight lock and bias lock
Heavyweight lock
Lock flag bit
Code demonstration
Lock summary
Lock elimination and lock coarsening
Lock elimination
Lock coarsening
AQS
What is it?
AbstractQueuedSynchronizer
The built-in FIFO queue is used to complete the queuing work of the resource acquisition thread, and an int class variable is used to represent the state of holding the lock
AQS related
What can I do
Locking will lead to blocking = > queuing is required if there is blocking, and queuing is necessary to realize queuing
What can I do: explain
AQS internal architecture
Official website explanation
AQS uses a volatile int type member variable to represent the synchronization State, completes the queuing of resource acquisition through the built-in FIFO queue, encapsulates each thread to preempt resources into a Node node to realize lock allocation, and modifies the State value through CAS.
ReentrantReadWriteLock
Introduction of interview questions
Lock evolution
What is a read-write lock
Lock degradation description
Read lock and write lock are mutually exclusive
Lock degradation
Lock degradation description
Java8 official website description
Can be downgraded
Non lockable upgrade
Demotion of read / write lock
The lock can be degraded and the program can execute normally
If the program cannot execute normally, it will get stuck, the write lock is not released, and the read lock cannot be obtained
Acquire read lock after holding write lock
Lock degradation of Oracle source code
StampedLock postmark lock
What is StampedLock
What is the problem of lock hunger
How to alleviate the problem of hunger
StampedLock optimistic read lock
Features of StampedLock
StampedLock three access modes
Code demonstration of three access modes
Disadvantages of StampedLock
StampedLock may cause deadlock. It is recommended not to use it at work just to cope with the interview