1.6 the lock
1.6.1 Synchronized
1 Use of Synchronized
Modified instance method: lock the current object instance, before entering the synchronization code to obtain the current object instance lock
Accessorizing static methods: Locking the current class applies to all object instances of the class because static members do not belong to any of the instance objects. So if thread A calls the non-static synchronized method of an instance object, and thread B calls the static synchronized method of the class that the instance object belongs to, this is allowed, and mutual exclusion does not occur. Because a lock used to access a static synchronized method is the current class lock, and a lock used to access a non-static synchronized method is the current instance object lock.
Modify code block: specifies the lock object, locks the given object, and obtains the lock for the given object before entering the synchronous code base.
2 Underlying Principles
① Synchronized statements
public class Synchronized Demo {
public void method(a) {
synchronized (this) {
System.out.println(Synchronized code block); }}}Copy the code
Notice that there are two MoniterExits
② Synchronized modified methods
public class Synchronize dDemo2 {
public synchronized void method(a) {
System.out.println("A synchronized method"); }}Copy the code
3 lock escalation
No lock state, can be biased
After the JVM starts up for a while, it starts up, initially generating objects in a biased state
Biased locking
biasThe first oneThe thread that gets the lock.
When thread 1 accesses the code block and acquires the lock object, it will record the threadID of the biased lock in the Java object header and stack frame, because the biased lock will not release the lock actively. Therefore, when thread 1 acquires the lock again in the future, it needs to compare whether the threadID of the current thread is consistent with the threadID in the Java object header. If they are the same (thread 1 still acquires the lock object), CAS is not required to lock and unlock the lock. If it is inconsistent (other threads, such as thread 2, compete for the lock object, and the biased lock is not actively released, so it is still the stored threadID of thread 1), then we need to check if thread 1 is alive as recorded in the Java object header. If not, then the lock object is reset to lock free. Other threads (thread 2) can compete to set it to a biased lock; If alive, then immediately find the thread (thread 1) stack frame of the information, if still need to continue to hold the lock object, so to suspend the current thread 1, cancel the biased locking, upgrade for lightweight lock, if thread 1 no longer use the lock object, then the lock object set as unlocked state, to a new thread.
Lightweight lock
Lightweight locks are locked through CAS.
JVM will create a space called Lock Record in the stack frame of the thread, copy the object header Mark Word into the space, and try to point the Lock Record pointer in the original object header Mark Word to the Lock Record through CAS. If successful, the thread holds the lock. If that fails, the thread spins (spin lock), which is upgraded to a heavyweight lock after a certain number of spins, at which point the thread is suspended by the kernel.
spinlocks
Before a lightweight lock bulges into a heavyweight lock, the thread spins to try to acquire the lock as it executes the Monitorenter instruction and enters the wait queue.
If the lock is not picked up after a certain number of spins (10), it is blocked and waiting for the kernel to schedule it. There is a context switch between kernel and user mode, which affects performance (the spin lock was introduced to reduce this overhead).
Since subsequent threads also spin first to try to acquire the lock, this would be unfair to those already blocked.
Heavyweight lock
A heavyweight lock operates on threads through the kernel. The frequent switching between kernel mode and user mode can seriously affect performance.
Upgrading to a heavyweight lock creates a Monitor object in the heap, points the Mark Word to the Monitor object, and adds threads to the EntrySet.
1.6.2 ReentryLock
The underlying layer is mainly an AQS and condition node
Node status
- int INITIAL = 0; // Initial state
- int CANCELLED = 1; // The current node is removed from the synchronization queue
- int SIGNAL = -1; // The thread of the successor node is in the wait state. If the current node releases the synchronization state, the successor node will be notified and the thread of the successor node will continue to run.
- int CONDITION = -2; The thread of the node waits on Condition. When another thread calls signal() on Condition, the node is transferred from the wait queue to the synchronization queue, and is added to the synchronization queue.
- int PROPAGATE = -3; // Indicates that the next shared synchronous state acquisition will be propagated unconditionally.
Source code analysis
Lock acquisition
// Top level entrance
public final void acquire(int arg) {
if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }/* tryAcquire() attempts to acquire the resource directly, and returns it if it succeeds. AddWaiter () adds this thread to the end of the wait queue and marks it in exclusive mode; AcquireQueued () causes the thread to block waiting for a resource in the queue and not return until the resource is acquired. Returns true if it was interrupted during the entire wait, false otherwise. If a thread has been interrupted while waiting, it does not respond. SelfInterrupt () is performed only after the resource has been retrieved, reclaiming the interrupt. * /
// Generate fetch lock
tryAcquire(int)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// Add to wait queue
addWaiter(Node mode)
private Node addWaiter(Node mode) {
// Construct nodes with the given schema. There are two types of mode: EXCLUSIVE and SHARED
Node node = new Node(Thread.currentThread(), mode);
// Try the fast way to go straight to the end of the queue.
Node pred = tail;
if(pred ! =null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
returnnode; }}// If you fail in the previous step, pass enQ to join the team.
enq(node);
return node;
}
private Node enq(final Node node) {
//CAS" spins "until it successfully joins the back of the queue
for (;;) {
Node t = tail;
if (t == null) { // If the queue is empty, create an empty flag node as head and point tail to it as well.
if (compareAndSetHead(new Node()))
tail = head;
} else {// Go to the end of the queue
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
returnt; }}}}Copy the code
The above code enters a waiting queue and needs to queue up for a number to obtain the lock
// Follow the method in line 4
// Wait in the queue to get the number (there is no rest to do) until you get the number
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;// flag whether the resource was successfully fetched
try {
boolean interrupted = false;// Indicate whether the wait has been interrupted
// Another spin!
for (;;) {
final Node p = node.predecessor();// Get the drive
// If the node is head, then it is eligible to attempt to fetch the resource (possibly because the boss woke up from the resource, or because it was interrupted).
if (p == head && tryAcquire(arg)) { //tryAcquire returns to ture
setHead(node);// After getting the resource, point head to the node. So head refers to the benchmarking node, which is the node from which the resource was obtained or null.
p.next = null; // node.prev is null in setHead, and head.next is null in setHead so that GC can reclaim the previous head node. It means that the node out of the team before taking the resource!
failed = false; // The resource was successfully obtained
return interrupted;// Return whether the waiting process was interrupted
}
// If you can rest, wait through park() until unpark(). If the uninterruptible condition is interrupted, it wakes up from park(), finds that the resource is not available, and continues into park() to wait.
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;// If the wait has been interrupted even once, mark interrupted as true}}finally {
if (failed) // If the resource is not retrieved during the wait (such as timeout, or interrupted in the case of interruptible), then cancel the node waiting in the queue.cancelAcquire(node); }}/ / pick up 20 lines
// This method is mainly used to check whether you are ready to rest (waiting), in case all the threads in front of the queue give up and just stand there.
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;// Get the state of the precursor
if (ws == Node.SIGNAL)
// If you have been told to inform yourself when you finish getting the number, you can rest at ease
return true;
if (ws > 0) {
/* * If the precursor gives up, keep looking until you find the nearest normal waiting state and place it behind it. * Note: the abandoned nodes, because they are "pushed" in front of them, form a no-reference chain and will be removed by the security uncle later (GC collection)! * /
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// If the precursor is working well, set the status of the precursor to SIGNAL and tell it to notify itself when it finishes retrieving the number. It could fail. He might have just been released!
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// join thread 21 to break
private final boolean parkAndCheckInterrupt(a) {
LockSupport.park(this);// Call park() to make the thread enter a waiting state
return Thread.interrupted();// If you are awakened, check to see if you are interrupted.
}
Copy the code
This is where the lock can be acquired
The main process
In the unfair state, first try to get directly
If the node cannot be obtained directly, it needs to be generated to be included in the waiting queue, and the state of the previous node needs to be changed to Sign (notice to wake me up). If it cannot be found, it will keep looking forward
The lock is acquired through an infinite loop of spin, trying to acquire the lock if the precursor is head, or blocking itself with park () if not
Release the lock
In unshared mode
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;// Find the header
if(h ! =null&& h.waitStatus ! =0)
unparkSuccessor(h);// Wake up the next thread in the wait queue
return true;
}
return false;
}
This method is used to wake up the next thread in the wait queue
private void unparkSuccessor(Node node) {
// Here, node is usually the node where the current thread is located.
int ws = node.waitStatus;
if (ws < 0)// set the state of the node where the current thread resides to zero, allowing failure.
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;// find the next node to wake up
if (s == null || s.waitStatus > 0) {// If empty or cancelled
s = null;
for(Node t = tail; t ! =null&& t ! = node; t = t.prev)// Look from back to front.
if (t.waitStatus <= 0)<=0; // <=0;
s = t;
}
if(s ! =null)
LockSupport.unpark(s.thread);/ / wake
}
Copy the code
In Shared mode
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);// Join the end of the queue
boolean failed = true;// Whether the success flag
try {
boolean interrupted = false;// Indicates whether the waiting process has been interrupted
for (;;) {
final Node p = node.predecessor();/ / precursor
if (p == head) {// If the thread is next to the head, node will wake up because the head is the thread that gets the resource
int r = tryAcquireShared(arg);// Try to obtain the resource
if (r >= 0) {/ / success
setHeadAndPropagate(node, r);// If you point the head at yourself, there are resources left to wake up subsequent threads
p.next = null; // help GC
if (interrupted)// If the wait is interrupted, the interrupt will be replaced.
selfInterrupt();
failed = false;
return; }}// Enter a waiting state, waiting for unpark() or interrupt()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; }}finally {
if(failed) cancelAcquire(node); }}// Add line 16, set the head node bit to itself, wake up subsequent threads,
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);//head points to yourself
// Wake up the next neighbor thread if there is more space left
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null|| s.isShared()) doReleaseShared(); }}/ / releases the lock
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {// Try to release resources
doReleaseShared();// Wake up the successor node
return true;
}
return false;
}
private void doReleaseShared(a) {
for (;;) {
Node h = head;
if(h ! =null&& h ! = tail) {int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if(! compareAndSetWaitStatus(h, Node.SIGNAL,0))
continue;
unparkSuccessor(h);// Wake up the successor
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)// Head changes
break; }}Copy the code
The main difference between shared and non-shared is that when the head node releases the lock and wakes up subsequent nodes, shared mode keeps waking up subsequent nodes, while non-shared mode does not
1.6.3 Related Components
Semaphore:
Allow multiple threads to access a resource simultaneously: Synchronized and ReentrantLock allow only one thread to access a resource at a time, and Semaphore allows multiple threads to access a resource simultaneously.
CountDownLatch:
CountDownLatch is a synchronization utility class that coordinates synchronization between multiple threads. This tool is usually used to control thread wait, it can make a particular thread wait until the end of the countdown
Bundle, and then start executing.
public class CountDownLatchExample1 {
// The number of files processed
private static final int threadCount = 6;
public static void main(String[] args) throws InterruptedException {
// Create a thread pool object with a fixed number of threads.
ExecutorService threadPool = Executors.newFixedThreadPool(10);
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadnum = i;
threadPool.execute(() -> {
try {
// Handle file operations. }catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Indicates that a file has been completedcountDownLatch.countDown(); }}); } countDownLatch.await(); threadPool.shutdown(); System.out.println("finish");
}
Copy the code
CyclicBarrier:
CyclicBarrier is very similar to CountDownLatch in that it can also implement technical waits between threads, but it is more complex and powerful than CountDownLatch. Main application scenarios and
CountDownLatch is similar. CyclicBarrier literally means CyclicBarrier. What it does is allow a group of threads to block when they reach a barrier (also known as a synchronization point), and the barrier will not open until the last thread reaches the barrier, and all threads blocked by the barrier will continue to work. The CyclicBarrier’s default constructor is CyclicBarrier(int parties), whose argument is the number of threads the barrier intercepts, and each thread calls the await() method to tell the CyclicBarrier that I have reached the barrier and the current thread is blocked.
ReadWriteLock (read-write lock)
public ReentrantReadWriteLock(a) {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
Copy the code
Change the state of the lock to 16 bits high and 16 bits low, respectively, indicating the lock status
Fair mode ==== is a team
Unfair Mode ====
Because the logic of acquiring read locks is complicated, we will summarize briefly here:
A. If no global lock exists, the current thread acquires the read lock
B. If the current global read lock is in place and there is no waiting thread in the queue, the current thread acquires the read lock
C. If the global write lock is currently occupied (and not by the current thread), the current thread is at the end of the queue
D. If the current thread is in the global read lock state and the first waiting thread in the wait queue wants to acquire the write lock, the current thread can obtain the read lock only if: The current thread has acquired the write lock and has not released it. The current thread has acquired the read lock, and this time it is just re-entering the read lock. In other cases the current thread is at the end of the queue. This is done partly for efficiency and partly to avoid starving threads that want to acquire write locks and never get a chance to execute
E. If the current global read lock status and the first waiting thread in the wait queue is not a write lock, the current thread can preempt the read lock
Acquiring write locks is relatively simple, as follows:
H. If there is no lock, the current thread acquires the write lock
I. If the current thread is in the global read lock state, the current thread ends the queue
J. If the global write lock is currently in, the queue ends unless reentrant obtains the write lock
Lock down
Assuming thread A has acquired the write lock, thread A can continue to acquire the read lock until the write lock is released. At this point line A has both read and write locks. After the write service of thread A is complete, the write lock is released but the read lock is not released. At this point the lock is degraded for thread A. Note that if thread A first acquired A read lock, it would not be able to upgrade to A write lock. You need to release the read lock before attempting to acquire the write lock
Lock upgrade === “not possible
Reentrant === “Once a reentrant operation occurs, it can be obtained directly without queuing
1.6.4 Differences between ReentrantLock and synchronized
1. Lock implementation
Synchronized is implemented by the JVM, while ReentrantLock is implemented by the JDK.
2. The performance
The new version of Java has many optimizations for synchronized, which is much the same as ReentrantLock, such as spinlocks.
3. Wait can be interrupted
When the thread holding the lock does not release the lock for a long time, the waiting thread can choose to abandon the wait and do something else instead.
ReentrantLock interrupts, while synchronized does not.
4. A fair lock
A fair lock means that when multiple threads are waiting for the same lock, they must obtain the lock in the sequence in which the lock was applied.
Synchronized locks are unfair, and reentrantLocks are unfair by default, but they can also be fair.
5. Lock binds multiple conditions
A ReentrantLock can bind multiple Condition objects simultaneously.
1.6.5 Interrupted, Interrupt, isInterrupted
interrupted()
Static methods: The internal implementation calls isInterrupted() of the current thread and resets the interrupted status of the current thread
isInterrupted()
IsInterrupted () is an instance method that isInterrupted() for the thread represented by the object calling the method and does not reset the interrupted status of the current thread
interrupt():
Method is used to interrupt a thread, and the state of the calling thread is set to “interrupt”.
Note: Calling interrupt () only stops the current thread. It does not actually stop the thread. It is up to the user to monitor the thread’s status and handle it . What this method actually does is throw an interrupt signal when the thread is blocked so that it can exit the blocked state.
More specifically, if a Thread is blocked by one of object. wait, Thread.join, or Thread.sleep, it receives an InterruptedException that terminates the blocked state prematurely.
public class Do {
public static void main(String[] args ) {
MyThread thread=new MyThread();
thread.start();
thread.interrupt();
System.out.println("First call thread.isinterrupted () :"+thread.isInterrupted());
System.out.println("Second call thread.isinterrupted () :+thread.isInterrupted());
// Test the interrupted () function
System.out.println("First call thread.interrupted() :"+thread.interrupted());
System.out.println("Second call thread.interrupted() :"+thread.interrupted());
System.out.println("Thread alive or not:"+thread.isAlive()); }} outputtrue
true
false
falseExplanation: the first two tests are their ownnewThe thread that comes out is tagged, and the tag is not refreshed, so yestrueThe last two tests were on the main thread, which is not tagged, so it's alwaysfalse
Copy the code
public class MyThread extends Thread {
@Override
public void run(a) {
for (int i = 0; i < 1000; i++) {
System.out.println("i="+(i+1));
if(this.isInterrupted()){
System.out.println("Interruption detected by this.isinterrupted ()");
System.out.println("First Interrupted ()"+this.interrupted());
System.out.println("Second Interrupted ()"+this.interrupted());
break;
}
}
System.out.println("I'm breaking out of the loop because I detected an interrupt, and the thread ends up here because there's nothing left."); }}public class Do {
public static void main(String[] args ) throws InterruptedException {
MyThread myThread=new MyThread();
myThread.start();
myThread.interrupt();
//sleep waits one second for myThread to run
Thread.currentThread().sleep(1000);
System.out.println(MyThread Whether the thread is alive:+myThread.isAlive()); }}Copy the code
public class InterruptionSleepThread implements Runnable {
@Override
public void run(a) {
try {
System.out.println("I'm going to sleep for 50 seconds. Can you stop me?");
Thread.sleep(50000);// Sleep for 50 seconds
} catch (InterruptedException e) {
System.out.println("The dormant thread receives the interrupt signal, uses the way that throws the exception to wake up this big uncle from the dream, ends the thread."); e.printStackTrace(); }}public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptionSleepThread());
thread.start();
Thread.sleep(2000);
thread.interrupt();
System.out.println("End of main thread"); }}Copy the code
reference
Locks upgrade and degrade
Lock escalation