Season 3 - java Basics

Keywords: Java

Java internal load - string constant pool

/**
 * @ Author wuyimin
 * @ Date 2021/9/13-19:34
 * @ Description
 * String::intern()Is a local method. Its function is to return the String in the String constant pool if it already contains a String equal to the secondary String object
 * String object reference of string. Otherwise, the string contained in this string object is added to the constant pool, and the reference of this string object is returned.
 */
public class InternDemo {
    //==The symbol represents the comparison of objects
    public static void main(String[] args) {
        String str = new StringBuilder("hua").append("wei").toString();
        System.out.println(str);
        System.out.println(str.intern());
        System.out.println(str==str.intern());//true
        System.out.println();
        String str2=new StringBuilder("ja").append("va").toString();
        //This object was created by ourselves
        System.out.println(str2);
        //This object is a java string that comes with the jdk
        System.out.println(str2.intern());
        System.out.println(str2==str2.intern());//false
    }
}

What is LockSupport

The basic thread blocking primitive used to create locks and other synchronization classes. Its park and unpark methods are used to block and unblock threads respectively, which is an improved and enhanced version of the wait for wake-up mechanism of wait/notify.

About the wait/notify method: it must be used in pairs in the synchronized code block or method (the synchronized keyword must be used), and it must wait before notify, otherwise the thread will be blocked all the time.

About await/signal method

These two methods are the method in condition. The target is synchronized wait/notify. Similarly, the thread must obtain and hold the lock. It must be in the lock block (synchronized or lock). It must wait before waking up before the thread can be awakened

For the LockSupport class, it uses a concept called permission to block and wake up threads. Each thread has a permission. Permission has only two values of 1 and 0, which can be regarded as a semaphore with an upper limit of 1.

A case study: it does not need to be used in the synchronous code block

public class LockSupportDemo {
    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "---come in");
            LockSupport.park();//Blocked.. Waiting for notification of release, a permit is required
            System.out.println(Thread.currentThread().getName() + "--- Awakened");
        }, "a");
        a.start();
        try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
        new Thread(()->{
           LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+"Issuance of licenses");
        },"b").start();
    }
}

a---come in
b issuance of permits
a --- Wake up

  It can issue a license before waking up the thread

public class LockSupportDemo {
    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            try{ TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){ e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName() + "---come in");
            LockSupport.park();//Blocked.. Waiting for notification of release, a permit is required
            System.out.println(Thread.currentThread().getName() + "--- Awakened");
        }, "a");
        a.start();
        new Thread(()->{
           LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+"Issuance of licenses");
        },"b").start();
    }
}

  b issuance of permits
a---come in
a --- Wake up

  Analysis: it calls the native method in the unsafe class -- primitive level. It can be understood that when entering the park method, you must have a license to pass. When calling the unpark method, a license will be issued. If a thread has a license, it can run normally, otherwise it will be blocked. After calling the park method, the license is consumed and released. Moreover, the cumulative value of unpark is at most 1. If you call park and unpark twice, it will always be blocked.

public class LockSupportDemo {
    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "---come in");
            LockSupport.park();//Blocked.. Waiting for notification of release, a permit is required
            LockSupport.park();//Blocked.. Waiting for notification of release, a permit is required
            System.out.println(Thread.currentThread().getName() + "--- Awakened");
        }, "a");
        a.start();
        new Thread(() -> {
            LockSupport.unpark(a);
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName() + "Issuance of licenses");
        }, "b").start();
    }
}

b issuance of permits
a---come in

Talk about AQS

Where is AQS? The full name of the static internal class in ReentrantLock is AbstractQueuedSynchronizer, which translates directly into abstract queue synchronizer.

Technical explanation of AQS? It is a heavyweight basic framework used to build locks or other synchronizer components and the cornerstone of the whole juc system. The queuing of resource acquisition threads is completed through the built-in FIFO queue, and the lock holding State is represented by a volatile int type variable. FIFO queue encapsulates each thread to preempt resources into a Node node to realize lock allocation, and modifies the State value through CAS. AQS=state+CLH queue (the element in the queue is Node (including waitState, waiting State, front pointer, rear pointer, etc.)

Start with ReentrantLock to explain AQS, and simulate what happens after the second thread enters

The implementation classes of Lock interface basically complete thread access control by aggregating a subclass of queue synchronizer

Lock method of unfair lock

  acquire method and try method are reversed. If false is returned, move on

  tryAcquire is a hook method. The template design pattern is used here. If the subclass does not implement my method, I will throw an exception directly. We can use ctrl+alt+B or the left mouse button to open the implementation class of this method

Check state after entering. State is a global concept and does not belong to a thread. If it is idle, set the current thread as the owner and return true

The second judgment is the reentrant lock. Skip here to explain that the above if is not satisfied, that is, the lock is occupied, and the lock is occupied. It depends on which thread occupies the lock. If the working thread obtains the lock again, it is allowed to obtain the lock again. This is called reentrant. Of course, after locking several times, the lock must be unlocked several times

Otherwise, return false. After returning false, enter the if judgment condition of the acquire method above and enter the addWaiter method

 

  addWaiter method, because this is the second thread and there is no node in the queue. As the first thread entering the queue, pred=tail=null. Next, enter enq method

enq method. At this time, the tail pointer is equal to null, so it starts to enter the if statement. You can see that there is a new empty node here. Therefore, the head node is not the first thread we enter the queue, but the node generated by the system itself

However, the current thread has not finished processing and enters the second cycle. At this time, the tail pointer is no longer empty (ghost node / sentry node). In else, the front pointer of the current node points to t (now ghost node) and then judges to set the tail pointer. Here, it is expected to change the original ghost node to the current node. After success, the tail pointer points to the current node, and t (the next node of the ghost node becomes the current node), and the later nodes join the queue in the same way.

 

Go to the outer method acquirequeueueueueueueueued of addWaiter. If the head node is the precursor node of the current node and tryAcquire successfully becomes the thread owner, you can enter the if judgment and exit the loop. Here, suppose that the first thread business is too long and the judgment fails. Enter the second if and enter the function shouldparkafterfailqacqacquire of its judgment condition (whether the thread needs to be suspended when the acquisition fails). The parameters are sentinel node and current node

Enter this method and check the waitstates of the sentinel node (this is the waiting state of each thread) Value, which is 0 by default, so enter the else statement, use the atomic method to change our waitstates to the value of - 1 of SIGNAL, and return false, because its external calling method is a spin method, and its waitstates value is - 1 the second time you enter this method, so you can enter the first if statement, return true, and enter the second if statement of the external method , enter the method parkAndCheckInterrupt. The design idea here is to let the thread try to see if it can become the owner. If it fails, change the state and enter the parkAndCheckInterrupt method to be suspended

  parkAndCheckInterrupt. You are familiar with LockSupport here. park indicates that this thread is suspended. In fact, this thread is really blocked here, and subsequent threads are blocked in a similar way

Now let's take a look at the unlock method

  A release method is called in the unlock method

Its tryRelease method is also a hook method in the template design mode. It enters the corresponding method of unfair lock, c=1-1=0, so free will change from false to true, set the current owner to null, and then set the status to 0

Return to the external method. At this time, the head node (sentinel node) is not equal to null, and the state of the head node is not 0. The waiting state of all nodes entering the queue blocking is - 1, and only the last node just entering the queue is 0. So at this time, we can enter the unparksuccess method. The current value is - 1, enter the first if loop, compare the values, and set the current node value to 0

If the next node of the sentinel node is not null, a pass will be issued. In that case, it should be issued in the order of fair lock. Why is it called non fair lock? This is because when releasing the lock, if a thread enters, the thread can directly get the lock without entering the queue.

 

  At this time, our second thread is still spinning in the acquiredQueued method. When the state changes, the tryAquire method returns true, and in this method, the second thread has become a new owner. In a sense, it has been out of the queue, and the second thread node left in the thread has no meaning.

The following steps are mainly to set the head node as the current node, and empty Thread and Prev to make it a new sentinel node

 

  After the whole code is executed, as shown in the figure, the original sentinel node will be slowly recycled by GC

Posted by jbrill on Tue, 14 Sep 2021 19:54:23 -0700