Principles of JUC concurrency

JUC concurrency

synchronized

lock object

There are three application scenarios for synchronized:

  • Acting on non-static instance methods: lock the current instance, and before entering the synchronization method, you need to obtain the lock of the current instance, which is one at this time 对象锁.
  • Acting on static instance methods: lock the class to which the instance belongs. Before entering the static synchronization method, you need to obtain the lock of the current class object, which is one at this time 类锁.
  • Act on the code block: lock the object configured in the brackets.

underlying principle

  • 1. The monitor (English: monitors, also known as the monitor) is a program structure in which multiple working threads formed by multiple subprograms (objects or modules) in the structure mutually exclusive access to 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 operating primitives are "encapsulated" inside an object) The monitor realizes that at a point in time, at most only one thread is executing A subroutine of the monitor . The monitor provides a mechanism. The monitor can be regarded as a software module, which encapsulates shared variables and operations on these shared variables to form a functional module with a certain interface. Monitors can be called to implement process-level concurrency control.
  • 2. Every object in Java is inherited from Object, and each Object object is associated with one . The monitor corresponds to a structure monitorin the bottom layer of the C language . There are attributes to record the thread holding the object, and there are attributes to record the acquisition of the thread. The number of monitor locks. Only one thread can hold an object at the same time . This is the basis for all objects in Java to become lock objects and to acquire locks repeatedly.ObjectMonitor_owner_countmonitor
    insert image description here
  • 3. Bytecode identification:
    • The implementation of the synchronized synchronization code block uses monitorenterand monitorexitinstructions to identify the acquired monitorand released objects monitor. Each time monitorenteryou enter, the number of lock reentries will increase by 1, and each time monitorexityou exit, the number of lock reentries will decrease by 1.
    • The bottom layer of the synchronized synchronization method uses ACC_SYNCHRONIZEDthe access flag to identify the synchronization method, and uses it ACC_STATICto distinguish whether the method is a static synchronization method, that is, the method marked with ACC_SYNCHRONIZEDand ACC_STATICis a static synchronization method, and ACC_SYNCHRONIZEDthe method marked with and is a normal synchronization method. Static synchronization methods will hold class objects monitor, while ordinary synchronization methods will hold instance objects monitor.

synchronized lock upgrade

Locks will cause performance degradation during concurrency. Java 8 optimizes lock upgrades for the synchd lock keyword.
Object lock upgrade process:

  • No lock (lock flag 01, and whether the lock flag is 0)
  • Biased lock (lock flag 01, and whether the biased lock flag is 1), the first 54 bits point to the current thread id
  • Lightweight lock (lock flag bit is 00), the first 62 bits point to the pointer of Lock Record in the thread stack
  • Heavyweight (spin) lock (lock flag bit is 10), the first 62 bits point to the pointer of the monitor object in the heap .
  • GC flag (the lock flag is 11)

insert image description here

insert image description here

no lock

The first 25 digits are unused, the
last 31 digits are all 0, and store the hash code. Only when there is a call,
1 digit of unused
and 4 digits of generational age are generated, so the maximum generational age can only be 15, because there are only 4 in the object header bit record.
Bias lock flag, no lock is 0
lock flag 01.

Bias lock

When thread A第一次竞争到锁,通过操作修改MarkWord中的偏向锁线程ID、偏向锁标志位,使其从无锁状态升级为偏向锁。

A thread holding a biased lock will never need to synchronize if there is no competition from other threads.

The emergence of biased locks is to solve the problem of improving performance when a thread performs synchronization. The id of the thread is recorded in the object header. If the id matches, there is no need to switch from user mode to kernel mode to lock and unlock.

  • When a thread with a non-biased thread id tries to acquire the lock:
    • If there is no competition, that is, the biased thread has exited to synchronize the code block, and the non-biased thread enters, then the object header will be set to a lock-free state, and the biased lock will be revoked, and the current thread will be biased again.
    • If a competition occurs, that is, the biased thread has not exited the synchronization code block, then the lock will be upgraded to a lightweight lock , and the competing thread will enter the spin state, waiting to obtain the lightweight lock.
  • If the object is already in a biased state and the hashcode() method is called , its biased state will be revoked and expanded into a heavyweight lock . The first 62 bits of the object header point to the location of the heavyweight lock, which stores the original hashcode.
  • If the wait() method is called in the biased lock state , the biased lock will also be directly upgraded to a heavyweight lock, because the wait() method is also unique to heavyweight locks

insert image description here

Due to the high maintenance cost of biased locks, Java 15 abolished biased locks. In the end, before JDK 15, the biased lock was enabled by default. Starting from JDK 15, the default is disabled, unless it is displayed through UseBiasedLocking

lightweight lock

Lightweight lock: Multi-thread competition, but there is at most one thread competition at any time, that is, there is no situation where lock competition is too fierce, and there is no thread blocking the cold. Lightweight locks are essentially spin locks.
insert image description here
insert image description here
The spin lock has a maximum number of spins. If the spin reaches a certain number of times, the spin lock will be upgraded to a heavyweight lock. The number of spins depends on the adaptive spin lock mechanism . The general principle of adaptive spin lock:

  • If the thread spins successfully, the maximum number of spins for the next time will increase, because the JVM believes that since the last time it succeeded, there is a high probability that it will succeed this time.
  • On the contrary, if the spin is rarely successful, the number of spins will be reduced or even not spin next time to avoid CPU idling.

heavyweight lock

Heavyweight lock, the first 62 bits of the object header point to the mutex.
insert image description here

JIT's optimization of locks: lock elimination and lock coarsening


The code below the JIT compiler (just-in-time compiler) creates a new object every time and locks it. In fact, locking is completely useless. JIT will optimize it and eliminate locks.
insert image description here
Lock coarsening is to increase the locking range of the lock. In the code below, multiple synchronization code blocks are connected and the lock objects are the same. The JIT compiler will perform lock coarsening and use a larger range of synchronized to replace multiple synchronization code blocks. , Avoid locking and releasing locks multiple times.
insert image description here

reentrantlock

Fair locks and unfair locks

insert image description here
Reentrantlock is an unfair lock by default. There are two main considerations:

  1. There is still a time difference between resuming the suspended thread and acquiring 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, the unfair lock can allow the real running thread to acquire the lock first , make full use of the CPU time slice, and minimize the CPU idle time.
  2. An important consideration when using multi-threading is the overhead of thread switching. When using unfair locks, when a thread requests a lock to obtain a synchronization state, and then releases the synchronization state, the probability that the thread that just released the lock obtains the synchronization state again at this moment It becomes very large, so the overhead of threads is reduced.

如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了。否则那就用公平锁,大家公平使用。

reentrant lock / recursive lock

The advantage of reentrant locks is that deadlocks can be avoided to a certain extent. If a method with a lock is called recursively, if a non-reentrant lock is used at this time, it will lock itself.

deadlock

deadlock condition

  • There is a lock with exclusive access
  • The acquired lock cannot be forcibly stripped
  • When the required lock is not fully acquired, it will hold the lock and wait.
  • There is a loop waiting for the lock to be released

Here is a case where in MySQL, because the gap locks of two transactions are compatible with each other, the insertion intention lock and the gap mutual exclusion lead to a deadlock case:
insert image description here

  • time1 stage: transaction A plus gap lock, range (20, 30)
  • time2 stage: transaction B plus gap lock, range (20, 30)
  • Time3 stage: Transaction A tries to add an intention lock to the position with id 25, but finds that transaction B has set a gap lock between (20,30), the lock fails, blocks, and waits for transaction B to release the gap lock.
  • Time4 stage: Transaction B tries to add an intention lock to the position with id 26, but finds that transaction A has set a gap lock between (20,30), the lock fails, blocks, and waits for transaction A to release the gap lock.

Transaction A and transaction B wait for each other to release the lock, which meets the four conditions of deadlock: mutual exclusion, possession and waiting, non-occupancy, and circular waiting, so a deadlock occurs.

How to troubleshoot deadlock? How to solve deadlock?

Command line version:

  1. jps -l
    lists all Java process numbers, find the process where the deadlock occurs, if it is 1123
  2. jstack 1123
    You can locate the deadlock situation by viewing the process stack information

Graphical tool version:
Using the jconsole tool, you can also locate the deadlock situation of the running Java process.

LockSupport and interrupt mechanism

interrupt mechanism

  • A thread should not be interrupted or stopped forcibly by other threads, but the thread should stop by itself and decide its own destiny.
  • There is no way to stop a thread immediately in Java, but stopping a thread is very important, such as canceling a time-consuming operation. Therefore, Java provides a negotiation mechanism for stopping threads-interruption, that is, the interrupt identification negotiation mechanism.
  • Interruption is just a cooperative negotiation mechanism. Java does not add any syntax to interruption, and the process of interruption needs to be implemented by the programmer. To interrupt a thread, you need to manually call the interrupt method of the thread, which only sets the interrupt flag of the thread object to true; then you need to write your own code to continuously detect the flag of the current thread, if it is true, Indicates that other threads request this thread to be interrupted. What to do at this time needs to be implemented by writing code yourself.
  • Each thread object has an interrupt flag, which is used to indicate whether the thread is interrupted; if the flag is true, it means interrupted, and if it is false, it means it is not interrupted; the thread flag is set to true by calling the interrupt method of the thread object ; It can be called from other threads or from its own thread.

Three major APIs related to interruption

insert image description here

  1. interrupt():
    • When a thread calls interrupt(), if the thread is in a normal active state, then the thread's interrupt flag will be set to true, nothing more, the thread with the interrupt flag set will continue to run normally, unaffected. 即interrupt()仅是设置中断标识,并不实际中断线程,需要被调用的线程进行配合。It's like you tell a person to shut up, but in the end whether that person shuts up or not depends on its cooperation.
    • If the thread is in 被阻塞的状态(such as sleep, wait, join, etc.), call the interrupt () method of the current thread object in another thread, then线程将立即退出被阻塞状态,中断状态标志位将被清除,并抛出一个InterruptedException异常。
    • For inactive threads, calling interrupt() has no effect.
  2. public boolean isInterrupted(): Check whether the interrupt flag bit of the current instance is set to interrupt.
  3. public static boolean interrupted(): A static method that calls isInterrupted() of the current thread and passes in parameters to clear the interrupt flag.

As can be seen from the source code, the instance method does not clear the interrupt status by default, while the static method does.
insert image description here

How to interrupt a running thread?

  1. Through the volatile keyword, inter-thread communication is performed, and the thread will read the latest value of the volatile keyword every time.
public class demo1 {
    
    
    private static volatile boolean isStop;
    public static void main(String[] args) {
    
    
        new Thread( () -> {
    
    
            while(!isStop) {
    
    }
            System.out.println(Thread.currentThread().getName() + "收到停止!");
        },"A").start();
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "发送停止信号!");
            isStop = true;
        }, "B").start();
    }
}

insert image description here

  1. Atomic Boolean via AtomicBoolean,
public class demo1 {
    
    
    private static volatile boolean isStop;
    private static AtomicBoolean flag = new AtomicBoolean(false);
    public static void main(String[] args) {
    
    
        new Thread( () -> {
    
    
//            while(!isStop) {}
            while(!flag.get()) {
    
    }
            System.out.println(Thread.currentThread().getName() + "收到停止!");
        },"A").start();
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "发送停止信号!");
//            isStop = true;
            flag.set(true);
        }, "B").start(
        );
    }
}
  1. Through the interrupt API that comes with the thread, the interrupt() method of thread 1 is used in thread 2, and the isInterrupted() method is used in thread 1 to monitor.
public class demo1 {
    
    
    private static volatile boolean isStop;
    private static AtomicBoolean flag = new AtomicBoolean(false);
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread( () -> {
    
    
//            while(!isStop) {}
//            while(!flag.get()) {}
            while (!Thread.currentThread().isInterrupted()) {
    
    }
            System.out.println(Thread.currentThread().getName() + "收到停止!");
        },"A");
        t1.start();
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "发送停止信号!");
//            isStop = true;
//            flag.set(true);
            t1.interrupt();
        }, "B").start(
        );
    }
}

LockSupport

insert image description here
Thread waiting and thread wakeup of synchronized and reentrantlock have the following limitations:

  1. The wait() and notify() methods of the object must be run in a synchronous code block or a synchronous method, and if they appear in pairs, they must wait first and then notify before they are ok.
  2. The await() method and signal() method of the condition built by reentrantlock must first obtain the reentrantlock lock before they can be used, and they appear in pairs, and must first await() and then signal();

Based on synchronized and reentrantlock, three threads print A, B, and C alternately:

package Interrupt;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class demo1 {
    
    
    public static void main(String[] args) {
    
    
        Data d = new Data();
        for (int i = 0; i < 3; ++i) {
    
    
            final int temp = i;
            new Thread( () -> {
    
    
                for (int j = 0; j < 10; ++j) {
    
    
                    d.print2(temp, (char) ('A' + temp));
                }
            }).start();
        }
    }
}
class Data {
    
    
    private int flag = 0;
    private final Object lock = new Object();
    private final Lock lk = new ReentrantLock();
    private final Condition cd = lk.newCondition();
    void print(int i, char letter)  {
    
    
        synchronized (lock) {
    
    
            try {
    
    
                while (i != flag) lock.wait();
                System.out.println(letter);
                flag = (flag + 1) % 3;
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                lock.notifyAll();
            }
        }
    }
    void print2(int i, char letter) {
    
    
        lk.lock();
        new Object();
        try {
    
    
            while (i != flag) cd.await();
            System.out.println(letter);
            flag = (flag + 1) % 3;
            cd.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }
}

LockSupport principle and advantages

  • LockSupport is a tool class in the concurrent package. It does not support construction and provides a bunch of static methods, such as park(), unpark(), etc.
  • LockSupport provides park() and unpark() methods to realize the process of blocking threads and unblocking threads. LockSupport has a permit association with each thread that uses it. Each thread has a related permit, and there is only one permit at most, and repeated calls to unpark will not accumulate certificates.
  • When calling park():
    • If there is a certificate, directly consume the certificate, and then exit normally.
    • If no credentials are available, it must block waiting for credentials to become available.
  • When calling unpark():
    • Add a credential to the specified thread, but there can only be one credential at most, and it cannot be accumulated.

The advantage of LockSupport is flexibility:

  • LockSupport's park() and unpark() methods do not need to be inside a synchronized code block, or hold a lock.
  • unpark() can be called before the park() method, so you don’t have to worry about the order of execution between threads, because if unpark() is executed first, and credentials are given to the specified thread in advance, then when the thread calls park(), it will be directly lowered. Validate existing certificates.

The following is an example where the main thread waits for the sub-thread tasks to finish executing, and then wakes up the main thread:
注意在使用时,LockSupport的unpark()方法需要指定线程,因此第一步,我们需要获取到主线程md

public class demo1 {
    
    
    public static void main(String[] args) {
    
    
        Thread mt = Thread.currentThread();
        new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                System.out.println("子线程执行完毕。去唤醒主线程");
                LockSupport.unpark(mt);
            }
        }).start();
        System.out.println("等待子线程执行完毕");
        LockSupport.park();
        System.out.println("主线程被唤醒");
    }
}

JMM

JMM (Java Memory Model, JMM for short) itself is an abstract concept and does not really exist. It only describes a set of conventions or specifications, through which each variable in the program (especially multithreading) is defined. Read and write access methods and determine when and how one thread's writing to a shared variable becomes visible to another thread. The key technical points 原子性、可见性和有序性revolve around multithreading .

happens-before (principle of prior occurrence)

In JMM, if the result of an operation execution requires visibility or code reordering of another operation, there must be a happens-before principle between the two operations.

For example:
insert image description here
If you want to ensure that x = 5 in the B thread is visible to the A thread, so that the assignment of y = x is correct, then the A thread operation must happen - before the B thread.

Due to the instruction rearrangement operation of the compiler, this will cause the order of program execution to be inconsistent with the order we actually write. Generally, it is necessary to rely on the volatile and synchronized keywords to ensure orderliness.
insert image description here
insert image description here

happens- before the general principle

  • If one operation happens-before another operation, then the execution result of the first operation will be visible to the second operation, and the execution order of the first operation is before the second operation.
  • There is a happens-before relationship between two operations, which does not mean that they must be executed in the order established by the happens-before principle. If the execution result after reordering is consistent with the execution result according to the happens-before relationship, then this Reordering is not illegal.

8 principles of happens-before

  1. Sequence rules: Within a thread: According to the code order, the operation written in the front occurs first before the operation written in the back.
  2. Locking rules: an unlock operation occurs first before the lock operation on the same lock (sequence in time);
  3. Volatile variable rule: A write operation to a volatile variable occurs first before a read operation on this variable occurs later (sequentially in time).
  4. Transfer rule: If operation A precedes B, and operation B precedes C, it must be concluded that operation A precedes C.
  5. Thread start rule: The start() method of the thread Thread object happens first in every action of this thread.
  6. Thread Interruption Rules: The call to the thread interrupt() method happens before the code of the interrupted thread detects the occurrence of an interrupt event. That is, call the interrupt() method to set the interrupt flag first, and then the occurrence of the interrupt can be detected.
  7. Thread termination rules: All operations in a thread occur before the termination detection of this thread. You can use isAlive() and other means to detect whether the thread has terminated execution.
  8. Object finalization rules: The initialization of an object (constructor execution end) occurs first at the beginning of its finalized () method, that is, the finalized () method cannot be called before the object is initialized.

CAS

CAS is a CPU atomic instruction (cmpxchg instruction), which will not cause the so-called data inconsistency problem. The underlying implementation of the CAS method (such as compareAndSwapXXX) provided by Unsafe is the CPU instruction cmpxchg.

When the cmpxchg command is executed, it will judge whether the current system is a multi-core system. If so, it will lock the bus. Only one thread can accelerate the success. After the lock is successful, the cas operation will be executed, which means that the atomicity of CAS is actually CPU To achieve exclusive , the exclusive time here is much shorter than using synchronized heavyweight locks, so the performance will be better in multi-threaded situations.

Unsafe

Unsafe is the core class of CAS. Since Java cannot directly access the underlying system, it needs to access it through a native method. The Unsafe class is equivalent to a backdoor. Its internal methods are all natively modified, and it can directly manipulate memory like a C pointer.

The compareAndSwapObject of the Unsafe class, relative to the function called in Java, has two more attributes related to memory operations, var1 and var2, var1 indicates the object to be operated, and var2 indicates the offset of the address of the operated object attribute.
insert image description here
Take the atomic class Int implemented by the unsafe class as an example:
when the execution of compareAndSwap() fails, it will fall into a loop, that is, spin, and only when the execution succeeds, it will jump out of the loop.
insert image description here

ThreadLocal

basic use

ThreadLocal provides thread local variables . These variables differ from normal variables because each thread that accesses the ThreadLocal instance (via its get or set method) has its own, independently initialized copy of the variable. A ThreadLocal instance is usually a private static field in a class where you want to associate state (for example, a user ID or transaction ID) with a thread.

Common method:
insert image description here
Set initialization method:
protected T initialValue()
The get() method of each thread accessing this value for the first time will return the result of this initialization method.
insert image description here

JDK8, you can use withInitial() to simplify the above writing:
insert image description here
Case: Multi-threaded simulation ticket purchase statistics

public class demo1 {
    
    
    public static void main(String[] args) {
    
    
        SaleData sd = new SaleData();
        for (int i = 0; i < 5; ++i) {
    
    
            new Thread(() -> {
    
    
                int j = Math.abs(new Random().nextInt()) % 20;
                while (j-- != 0) {
    
    
                    sd.sale();
                }
                System.out.println(Thread.currentThread().getName() + "卖出:" + sd.saleNum.get());
            }).start();
        }
    }
}
class SaleData {
    
    
    ThreadLocal<Integer> saleNum = ThreadLocal.withInitial(() -> 0);
    public void sale() {
    
    
        saleNum.set(saleNum.get() + 1);
    }
}

Terms of Use

Each thread has its own thread stack, and the size of the stack space is valid, so 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, it will cause The accumulation of ThreadLocal variables may affect subsequent business logic and cause memory leaks.

Try to use it in the try - finally block, use it in try, and remove it in finally.

public class demo1 {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        SaleData sd = new SaleData();
        try {
    
    
            for (int i = 0; i < 10; i++) {
    
    
                threadPool.submit(() -> {
    
    
                    int r = Math.abs(new Random().nextInt() % 15);
                    for (int j = 0; j < r; ++j) {
    
    
                        sd.sale();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出:" + sd.saleNum.get());
                });
            }
        } finally {
    
    
            // 必须在用完后remove
            sd.saleNum.remove();
            threadPool.shutdown();
        }
    }
}
class SaleData {
    
    
    ThreadLocal<Integer> saleNum = ThreadLocal.withInitial(() -> 0);
    public void sale() {
    
    
        saleNum.set(saleNum.get() + 1);
    }
}

The relationship between Thread, ThreadLocal, and ThreadLocalMap?

There is a member of ThreadLocalMap in Thread,
each Entry of ThreadLocalMap uses the weak reference of the ThreadLocal instance as the key, and the task object as the value.

insert image description here

insert image description here

insert image description here

Why does the entry key of ThreadLocalMap use threadlocal weak reference? What happens if the weak reference is GC?

Suppose we set a threadlocal variable t1 in a method and store it in Thread. This variable is a strong reference, but the one stored in ThreadlocalMap is a weak reference.

 public void func1() {
    
    
     ThreadLocal<String> t1 = new ThreadLocal<>();
     t1.set("1111");
 }

insert image description here
Why use weak references?
When func1 is executed and the stack frame is destroyed, the strong reference to t1 is gone. But at this time, the key reference of an entry in the ThreadLocalMap of the thread still points to this object. If the key reference is a weak reference, the threadlocal object pointed to by the key and the pointed object cannot be recycled by gc, resulting in a memory leak.

With weak references, the weak reference object will be found every time gc, and memory leaks will not occur.

当gc发生后,存在ThreadlocalMap中的Entry的key会变为null,那么其对应的value就无法被访问,为解决该问题。threadlocal的get()、set()、remove方法,都会寻找key为null的脏Entry,然后对它进行删除。

Object memory layout and object headers

The storage layout of objects in heap memory can be divided into three parts: object header (Header), instance data (Instance Data) and alignment padding (Padding) .

The purpose of alignment padding is to ensure multiples of 8 bytes.
insert image description here
The array object has more fields to record the length of the array in the object header than the Java object.

insert image description here

object header

The object header is divided into two parts:

  • Object Mark (Mark Word)
    • hash code
    • GC mark
    • GC times
    • sync lock flag
    • biased lock holder
  • Type pointer (Class Pointer): Points to the class meta information of the instance in the method area.

insert image description here
In a 64-bit system, the object mark (Mark Word) in the object header occupies 8 bytes, and the type pointer occupies 8 bytes, a total of 16 bytes.
Mark Word 默认存储对象的哈希code、分代年龄和锁标志位等信息,这些信息都是与对象自身定义无关的数据,所有MarkWord 被设计成一个非固定的数据结构,以便在极小的空间内存储尽量多的数据,它会根据对象的状态复用自己的存储空间,也就是说再运行期间MarkWord里的存储数据会随着的锁的标志位的变化而变化
insert image description here

insert image description here
对象分代年龄使用4个bit位,最大是15

classifier information

Class meta information is created in the metaspace when the class is loaded. (The concept of meta space after Java8 is used to store class meta information, static variables, constant pools, etc.)
Each instance object has a pointer class pointer pointing to its class meta information, and the virtual machine uses this pointer to determine whether the object is instance of which class.
insert image description here

AQS

AQS (AbstractQueuedSynchronizer, Abstract Queue Synchronizer) as a whole is an abstract FIFO queue to complete the queuing work of resource acquisition threads, and express the status of holding locks through an int type state.

Every time you try to hold a lock, check whether the state is 0, and if it is 0, change the state to 1, indicating that the lock is held.
insert image description here
AQS is the cornerstone of JUC. ReentrantLock, CountDownLatch, ReentrantReadWriteLock, and Semaphore in JUC are all implemented based on AQS.

The lock is for the user of the lock, and the AQS (Abstract Queue Synchronizer) is for the designer of the lock.

state and CLH queue

The state variable is a volatile modified int variable. If it is 0, it means that it is free, and if it is greater than or equal to 1, it means that the lock is occupied.

CLH queue: CLH is the abbreviation of three big cows, and the essence of the queue is a double-ended queue.

insert image description here

AQS's own attributes and Node nodes

insert image description here

ReentrantLock implementation principle

How is fairness and unfairness achieved?

ReentrantLock maintains 3 internal classes: Sync, FairSync, and NonFairSync.

insert image description here
When calling the lock method of Reentrant, it actually calls the lock method of sync, and sync then calls the lock method of fairSync or NonFairSync.

这里可以发现公平锁与非公平锁的第一个区别:
非公平锁: 调用lock时,首先就会执行一次CAS,如果失败,才会进入acquire方法 公平锁: 不会进行CAS抢占锁,直接进入acquire方法
insert image description here

The specific tryAcquire method is handed over to the subclass to implement:
insert image description here
the tryAcquire() method of the fair lock has an additional restriction when obtaining the synchronization state: hasQueuedPredecessors(),判断等待队列中是否存在有效前驱节点,只有队列为空或者当前线程节点是AQS中的第一个节点,则返回false,代表后续可以去获取锁;否则其他情况都表示,已经有前驱节点,返回true代表后续不能获取锁!!

Guess you like

Origin blog.csdn.net/baiduwaimai/article/details/132140939