Redis cache: ten thousand words long! Starting from the bottom, it will take you to understand concurrent programming

![](https://upload-images.jianshu.io/upload_images/24195226-95f672f1095c9a4c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


We all know`StringBuffer`It is thread safe because its key methods are added`synchronized`,However, it can be seen from the print result that the lock is eliminated. because`buffer`This reference will only be used in`main`Method, which cannot be referenced by other threads(Because it is a local variable, the stack is private),therefore`buffer`It is an impossible resource to share,`JVM`Will be automatically eliminated`StringBuffer`The lock inside the object.

## Lock coarsening

/**

  • @author yhd
  • @createtime 2020/9/8 20:48
    */
    public class Demo3 {
    public static void main(String[] args) {
    int i=0;
    StringBuffer buffer = new StringBuffer();
    while (i<100){
    buffer.append(i);
    i++;
    }
    System.out.println(buffer.toString());
    System.out.println(ClassLayout.parseInstance(buffer).toPrintable());
    }
    }
`JVM`It will be detected that such a series of operations lock the same object(`while` Execute 100 times in the cycle `append`,If there is no lock coarsening, lock it 100 times/Unlock), now `JVM` The scope of locking will be coarsened to the outside of this series of operations (for example `while` Unreal body), so that this series of operations only need to add a lock once.

## AQS

Studied`AQS`One day, I finally found his entrance. Next, let's look at my thoughts:

## Multi thread operation sharing data problem

/**

  • @author yhd

  • @createtime 2020/9/8 8:11
    */
    public class Demo1 {
    public static int m=0;

    public static void main(String[] args)throws Exception {
    Thread []threads=new Thread[100];
    for (int i = 0; i < threads.length; i++) {
    threads[i]=new Thread(()->{
    for (int j = 0; j < 100; j++) {
    m++;
    }
    });
    }
    for (Thread t :threads) t.start();
    for (Thread t :threads) t.join();// End of thread sequence
    System.out.println(m);
    }
    }

There is no doubt that this code has thread safety problems. As long as you know a little about concurrent programming, you can see it. So how can we solve it?

**use synchronized To solve**

/**

  • @author yhd

  • @createtime 2020/9/8 8:32
    */
    public class Demo2 {

    public static int m=0;

    public static void main(String[] args)throws Exception {
    Thread []threads=new Thread[100];
    for (int i = 0; i < threads.length; i++) {
    threads[i]=new Thread(()->{
    synchronized (Demo2.class) {
    for (int j = 0; j < 100; j++) {
    m++;
    }
    }
    });
    }
    for (Thread t :threads) t.start();
    for (Thread t :threads) t.join();// End of thread sequence
    System.out.println(m);
    }

This solves the problem of thread safety, but there is also a problem, synchronized Is the operating system level approach, that is, the need jvm And operating system(Switching between user mode and kernel mode),This actually affects efficiency. Another solution:

**use ReentrantLock To solve**

/**

  • @author yhd

  • @createtime 2020/9/8 8:41
    */
    public class Demo3 {

    public static int m=0;
    public static Lock lock=new ReentrantLock();
    public static void main(String[] args)throws Exception {
    Thread []threads=new Thread[100];
    for (int i = 0; i < threads.length; i++) {
    threads[i]=new Thread(()->{

             try {
                 lock.lock();
                 for (int j = 0; j < 100; j++) {
                         m++;
                 }
             } finally {
                 lock.unlock();
             }
         });
     }
     for (Thread t :threads) t.start();
     for (Thread t :threads) t.join();//End of thread sequence
     System.out.println(m);
    

    }
    }

So how does the bottom layer do this? Next, follow the source code.

public ReentrantLock() {
    sync = new NonfairSync();
}
this sync What is it? Keep chasing

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
It's actually`ReentrantLock`An inner class of inherits`Sync`,And the method inside him is actually called`Sync`Methods. So the goal is clear. We can have a look`Sync`.  this`Sync`Source code:

abstract static class Sync extends AbstractQueuedSynchronizer

It can be seen that, in fact`ReentrantLock`Is to use`AbstractQueuedSynchronizer`that is`AQS`To achieve.

## AbstractQueuedSynchronizer overview

There is an inner class inside this class`Node`

static final class Node {
static final Node SHARED = new Node();
volatile Node prev;// Precursor pointer
volatile Node next;// Successor pointer
volatile Thread thread;// Current thread
private transient volatile Node head;// Head node
private transient volatile Node tail;// Tail node
private volatile int state;// Lock status: 1 if locking is successful, and 0 if re-entry + 1 is unlocked
...
}

I thought of it when I saw it`LinkedHashMap`,In fact, it is similar to maintaining a two-way linked list, and each node is a thread.

![](https://upload-images.jianshu.io/upload_images/24195226-6f5395f4acfffc0d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


It maintains a volatile int state(On behalf of shared resources) and a FIFO Thread waiting queue (this queue will be entered when multi-threaded contention resources are blocked). Here volatile Is the core keyword, specific volatile The semantics of are not described here. state There are three ways to access:

getState()
setState()
compareAndSetState()
`AQS`Define two resource sharing methods:`Exclusive`(Exclusive, only one thread can execute, such as`ReentrantLock`)and`Share`(Shared, multiple threads can execute simultaneously, such as`Semaphore/CountDownLatch`).    Different custom synchronizers compete for shared resources in different ways. When implementing a custom synchronizer, you only need to implement shared resources state As for the maintenance of the specific thread waiting queue (for example, failed to obtain resources and join the queue)/Wake up, get out of the team, etc.), AQS It has been implemented at the top level. The following methods are mainly implemented when customizing the synchronizer:

Ishldexclusively(): whether the thread is monopolizing resources. You only need to implement condition.
tryAcquire(int): exclusive mode. When trying to obtain resources, it returns true if successful, and false if failed.
tryRelease(int): exclusive mode. When attempting to release resources, it returns true if successful, and false if failed.
Tryacquiresered (int): sharing mode. Try to get the resource. A negative number indicates failure; 0 indicates success, but there are no available resources left; A positive number indicates success with resources remaining.
Tryrereleaseshared (int): sharing mode. Try to release the resource. If wake-up is allowed after release, the subsequent waiting nodes will return true; otherwise, return false.

with`ReentrantLock`For example, state Initializes to 0, indicating an unlocked state. A thread  lock()Called when`tryAcquire()`Monopolize the lock and state+1. After that, other threads`tryAcquire()`Will fail until A thread  unlock()reach state=0(That is, until the lock is released, other threads have a chance to acquire the lock. Of course, before releasing the lock, A The thread itself can repeatedly acquire this lock( state Will accumulate), which is the concept of reentrant. However, it should be noted that the number of times to obtain should be released, so as to ensure state Can go back to zero.

Again`CountDownLatch`For example, tasks are divided into N Sub threads to execute, state Also initialized to N(be careful N To be consistent with the number of threads). this N The sub threads are executed in parallel. After each sub thread is executed`countDown()`once, state meeting`CAS`Minus 1. Wait until all child threads are executed(Namely state=0),meeting`unpark()`The main calling thread, and then the main calling thread will await()Function returns to continue the remaining actions.

In general, custom synchronizers are either exclusive or shared, and they only need to be implemented`tryAcquire-tryRelease`,`tryAcquireShared-tryReleaseShared`Just one of them. but AQS It also supports the user-defined synchronizer to realize exclusive and sharing at the same time, such as`ReentrantReadWriteLock`. 

## Source code interpretation and execution process analysis

## Exclusive lock

acquire(int)== This method is the top-level entry for threads to obtain shared resources in exclusive mode. If the resource is obtained, the thread returns directly. Otherwise, it enters the waiting queue until the resource is obtained, and the impact of interruption is ignored in the whole process. This is exactly lock()Of course, the semantics of is not limited to lock(). After obtaining the resource, the thread can execute its critical area code.

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
The function flow is as follows:

`tryAcquire()`Try to obtain resources directly, and return directly if successful; `addWaiter()`Add the thread to the tail of the waiting queue and mark it as exclusive mode; `acquireQueued()`Enable the thread to obtain resources in the waiting queue and return until the resources are obtained. If it is interrupted during the whole waiting process, return true,Otherwise return false. If a thread is interrupted while waiting, it does not respond. Only after obtaining resources can self interruption be carried out selfInterrupt(),Make up the interruption. **tryAcquire(int)** This method attempts to acquire an exclusive resource. If the acquisition is successful, it returns directly true,Otherwise, return directly false. This is exactly tryLock()The meaning of, or that sentence, of course, is not limited to tryLock(). 

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
At first, I thought foolishly why I threw the exception directly. Later, I reacted. Isn't this a custom method? AQS Only one interface is defined here, and the acquisition of specific resources is implemented by the user-defined synchronizer. **addWaiter(Node)** This method is used to add the current thread to the tail of the waiting queue and return the node where the current thread is located.

private Node addWaiter(Node mode) {
 //Construct nodes in a given pattern. There are two mode s: EXCLUSIVE and SHARED
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
Node Node is the encapsulation of each thread accessing synchronization code, which includes the thread itself to be synchronized and the status of the thread, such as whether it is blocked, whether it is waiting for wake-up, whether it has been cancelled, etc. variable waitStatus Indicates that it is currently encapsulated into Node There are four values for the waiting state of a node CANCELLED,SIGNAL,CONDITION,PROPAGATE. 

`CANCELLED`: If the value is 1, the thread waiting in the synchronization queue times out or is interrupted. It needs to be cancelled from the synchronization queue Node Node of waitStatus by CANCELLED,That is, the end state. After entering this state, the node will not change again.

`SIGNAL`: Value is-1,When the thread of the successor node identified as the wake-up waiting state releases the synchronization lock or is cancelled, the thread of the successor node will be notified to execute. To put it bluntly, it is in the wake-up state. As long as the predecessor node releases the lock, it will notify that the ID is SIGNAL Thread execution of the successor node of the state.

`CONDITION`: Value is-2,And Condition The node identified is in the waiting queue, and the thread of the node is waiting Condition When other threads call Condition of signal()After the method, CONDITION The node in state will be transferred from the waiting queue to the synchronization queue, waiting to obtain the synchronization lock.

`PROPAGATE`: Value is-3,Related to the sharing mode, in which the thread of the state identification node is in the runnable state.

`0`Status: a value of 0 represents the initialization status.

AQS When judging the status, use waitStatus>0 Indicates cancel status, and waitStatus<0 Indicates a valid status.

== enq(Node)== This method is used to node Join the tail of the team.

private Node enq(final Node node) {
    for (;;) {//spin
        Node t = tail;
        if (t == null) { // Must initialize
        // If the queue is empty, create an empty flag node as the head node and point the tail to it
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {//Normally placed at the end of the queue
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
`cas spin volatile variable` **acquireQueued(Node, int)** adopt tryAcquire()and addWaiter(),The thread failed to obtain resources and has been placed at the end of the waiting queue. Smart, you should immediately be able to think of what the thread should do next: enter the waiting state to rest until other threads wake up after completely releasing resources, get the resources, and then you can do what you want to do. Yes, that's it! Is it a little similar to queuing up for a number in a hospital~~acquireQueued()This is what you do: queue up in the waiting queue to get the number (there is nothing else to rest in the middle), and then return until you get the number. This function is very critical. Go to the source code:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//Mark whether the lock has been obtained
    try {
        boolean interrupted = false;//Mark whether the waiting process has been interrupted
        for (;;) {
            final Node p = node.predecessor();//Get the precursor node
            //If the precursor is head, that is, the node has become the second child, then it is qualified to try to obtain resources (the boss may wake himself up after releasing resources, or it may be interrupt ed of course).
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                //After getting the resource, point the head to the node. Therefore, the benchmark node referred to in head is the node or null that currently obtains the resource.
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //If you can rest, you will enter the waiting state until you are unpark()
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;//If the waiting process is interrupted even once, mark interrupted as true
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
**shouldParkAfterFailedAcquire(Node, Node)** This method is mainly used to check the status and see if you can really rest.

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//Get the status of the precursor
    if (ws == Node.SIGNAL)
        //If you have told the precursor to inform yourself after taking the number, you can rest at ease
        return true;
    if (ws > 0) {
       /*
       * If the precursor gives up, keep looking until you find the latest normal waiting state and stand behind it.
       * Note: those abandoned nodes are "plugged" in front of them by themselves, which is equivalent to forming a reference free chain. Later, they will be driven away by the security uncle (GC recycling)!
      */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //If the precursor is normal, set the status of the precursor to SIGNAL and tell it to notify itself after taking the number. It may fail. They may have just released it!
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
In the whole process, if the status of the precursor node is not SIGNAL,Then you can't rest at ease. You need to find a safe rest point. At the same time, you can try again to see if it's your turn to take the number. **parkAndCheckInterrupt()** If the thread finds a safe rest point, it can rest at ease. This method is to let the thread rest and really enter the waiting state.

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

``

Thread.interrupted() clears the interrupt flag bit of the current thread.
The whole process of obtaining locks:
1. If the attempt to obtain the lock is successful, return directly.
2. If it fails, first add it to the end of the waiting queue and mark it as exclusive mode.
3. After trying to obtain a lock, if you still can't get it, go to rest. When you have a chance (it's your turn, you will be unpark()) will try to obtain resources. Return after obtaining the resource. If it is interrupted during the whole waiting process, it returns true; otherwise, it returns false.
4. If the thread is interrupted during waiting, it will not respond. Self interrupt () is performed only after the resource is obtained, and the interrupt is supplemented.
This is reentrantlock Process of lock()

release(int)
This method is the top-level entry for threads to release shared resources in exclusive mode. It will release the specified amount of resources. If it is completely released (i.e. state=0), it will wake up other threads in the waiting queue to obtain resources.

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
                //Wake up the next thread in the waiting queue
            return true;
        }
        return false;
    }


It determines whether the thread has completed releasing resources according to the return value of tryRelease()! Therefore, the user-defined synchronizer should be clear about this when designing tryRelease()!! tryRelease(int) this method attempts to release a specified amount of resources.

    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }


You still need to write the AQS implementation class yourself. Unparksuccess (node) this method is used to wake up the next thread in the waiting queue.

    private void unparkSuccessor(Node node) {
        //Here, node is generally the node where the current thread is located.
        int ws = node.waitStatus;
        if (ws < 0)
            //Set the node state of the current thread to zero, allowing failure.
            compareAndSetWaitStatus(node, ws, 0);

        //Find the next node to wake up s
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {//If empty or cancelled
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)//It can be seen from here that nodes with < = 0 are still valid nodes.
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);//awaken
    }


Wake up the first thread in the waiting queue with unpark(). Process of releasing lock 1 Releases the resource of the specified lock and returns the result. 2. If it is released successfully, wake up the first thread in the waiting queue that has not given up. 3. If it fails, false is returned.

Shared lock

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }


This method is used to add the current thread to the rest at the end of the waiting queue. It will not return until other threads release resources to wake themselves up and successfully get the corresponding amount of resources.

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//success
                        setHeadAndPropagate(node, r);//Point the head to yourself, and there are still remaining resources to wake up the subsequent thread
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }


Compared with the exclusive mode, only the thread here is head Next ("second" ), will try to obtain resources. If there is any left, it will wake up future teammates. Then the problem comes. If the boss releases 5 resources after running out, the second needs 6, the third needs 1, and the fourth needs 2. The boss wakes up the second child first. When the second child sees that the resources are insufficient, does he give the resources to the third child or not? The answer is no! The second will continue to park() and wait for other threads to release resources, and will not wake up the third and fourth. In exclusive mode, there is only one thread to execute at the same time; However, in the shared mode, multiple threads can be executed at the same time. Now, due to the large resource demand of the second, the third and fourth with a small amount are also stuck. Of course, this is not a problem, but AQS guarantees to wake up in strict accordance with the queue order (fairness is guaranteed, but concurrency is reduced). = = setHeadAndPropagate(Node, int)==

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }


== doReleaseShared()==

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }


Custom lock

Different custom synchronizers compete for shared resources in different ways. When implementing the user-defined synchronizer, you only need to implement the acquisition and release methods of shared resource state. As for the maintenance of specific thread waiting queue (such as failed resource acquisition, queue entry / wake-up, etc.), AQS has been implemented at the top level. The user-defined synchronizer mainly implements the following methods:

    isHeldExclusively(): Whether the thread is monopolizing resources. Only use condition To achieve it.
    tryAcquire(int): Exclusive mode. An attempt to obtain a resource is returned if successful true,Return if failed false. 
    tryRelease(int): Exclusive mode. Attempts to free resources, returns if successful true,Return if failed false. 
    tryAcquireShared(int): Sharing mode. Try to get the resource. A negative number indicates failure; 0 indicates success, but there are no available resources left; A positive number indicates success with resources remaining.
    tryReleaseShared(int): Sharing mode. Try to release the resource. If the resource is allowed to wake up after release, wait for the node to return true,Otherwise return false. 


Customize a simple lock

/**
 * @author yhd
 * @createtime 2020/9/8 9:44
 */
public class MLock implements Lock {
    private AbstractQueuedSynchronizer sync=new Sync();
    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    //Customize an exclusive lock
    private class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;


# Finally, how should we learn?

**1,Watch videos for systematic learning**

In recent years Crud Experience, let me understand that I am really a fighter in the chicken, because Crud,As a result, their technology is fragmented, not deep enough and not systematic enough, so it is necessary to study again. What I lack is systematic knowledge, poor structural framework and ideas, so learning through video is more effective and comprehensive. About video learning, I can recommend it B Stop to learn, B There are many learning videos on the website. The only disadvantage is that it is free and easy to be outdated.

In addition, I have also collected several sets of video materials lying on the online disk. I can share them with you if necessary:

![1 Years and a half of experience, 2 academic degrees, Curd The background is 30 K,My meituan Offer Finally](https://img-blog.csdnimg.cn/img_convert/59569ae6c88695e0dc7cc749f66014b6.png)

**2,Read the source code, read the actual combat notes, and learn the great God idea**

"Programming language is the programmer's way of expression, and architecture is the programmer's cognition of the world ". Therefore, if programmers want to quickly recognize and learn architecture, it is essential to read the source code. Reading the source code is to solve the problem + Understand things, more importantly: see the idea behind the source code; Programmers say: read ten thousand lines of source code and practice ten thousand lines.

Spring In depth analysis of source code:

![1 Years and a half of experience, 2 academic degrees, Curd The background is 30 K,My meituan Offer Finally](https://img-blog.csdnimg.cn/img_convert/18aa1bf64527b019561188427118ca00.png)

Mybatis 3 In depth analysis of source code:

![1 Years and a half of experience, 2 academic degrees, Curd The background is 30 K,My meituan Offer Finally](https://img-blog.csdnimg.cn/img_convert/c94b3f2f286c7990cd2bf4e0d0cb9fc3.png)

Redis Study notes:

![1 Years and a half of experience, 2 academic degrees, Curd The background is 30 K,My meituan Offer Finally](https://img-blog.csdnimg.cn/img_convert/e3e81600df834d35de63fad72242e32a.png)

Spring Boot core technology -Notes:

![1 Years and a half of experience, 2 academic degrees, Curd The background is 30 K,My meituan Offer Finally](https://img-blog.csdnimg.cn/img_convert/b2c32afd4c7be63e65d8fb55f32abcb3.png)

**3,On the eve of the interview, brush questions and sprint**

One week before the interview, you can start to brush questions and sprint. Please remember, when you brush questions, technology takes precedence, and the algorithm depends on some basic ones, such as sorting, etc., while intelligence questions are generally not asked unless they are school moves.

As for the interview questions, I personally prepared a set of systematic interview questions to help you draw inferences from one example:

![1 Years and a half of experience, 2 academic degrees, Curd The background is 30 K,My meituan Offer Finally](https://img-blog.csdnimg.cn/img_convert/ecca26959f1f68f48e8cb86cd0301b53.png)

Only with perfect skills, no worries about employment, and "all kinds of things can not be carried away", learning from industry only has the final say in the years of classroom, but is an uninterrupted thing in the journey of life.

Life is short, don't muddle through life, don't make do with it.

**Data collection method:[Click the blue portal to receive the above information for free](https://gitee.com/vip204888/java-p7)**

What is involved in the content of the article Java Interview questions, source code documents, technical notes and other learning materials can be shared to everyone for free. You just need to do it and give more support!

In depth analysis of source code:

[External chain picture transfer...(img-PbPbNROu-1628139638931)]

Mybatis 3 In depth analysis of source code:

[External chain picture transfer...(img-BuaQYvto-1628139638933)]

Redis Study notes:

[External chain picture transfer...(img-Jr6q12s2-1628139638935)]

Spring Boot core technology -Notes:

[External chain picture transfer...(img-WGdImeQE-1628139638937)]

**3,On the eve of the interview, brush questions and sprint**

One week before the interview, you can start to brush questions and sprint. Please remember, when you brush questions, technology takes precedence, and the algorithm depends on some basic ones, such as sorting, etc., while intelligence questions are generally not asked unless they are school moves.

As for the interview questions, I personally prepared a set of systematic interview questions to help you draw inferences from one example:

[External chain picture transfer...(img-q0aI0cq6-1628139638938)]

Only with perfect skills, no worries about employment, and "all kinds of things can not be carried away", learning from industry only has the final say in the years of classroom, but is an uninterrupted thing in the journey of life.

Life is short, don't muddle through life, don't make do with it.

**Data collection method:[Click the blue portal to receive the above information for free](https://gitee.com/vip204888/java-p7)**

What is involved in the content of the article Java Interview questions, source code documents, technical notes and other learning materials can be shared to everyone for free. You just need to do it and give more support!

Keywords: Java Back-end Interview Programmer

Added by hollyspringer on Thu, 30 Dec 2021 04:48:40 +0200