Learning notes on the practice of building high concurrency system with JUC

Posted by andynick on Mon, 10 Jan 2022 13:36:46 +0100

JUC concurrent programming - Dachang

Course description

Thread basics review

Why is multithreading so important

Hardware: Moore's law failure

Software: production requirements for asynchronous callback

start a thread

Java multithreading related concepts


User thread and daemon thread

Even the simplest HelloWord program will have two threads: user thread (main) and daemon thread (GC garbage collection thread)

CompletableFuture

Future and Callable interfaces

The Future interface defines some methods for operating asynchronous task execution, such as obtaining the execution result of asynchronous task, canceling task execution, judging whether the task is cancelled, judging whether the task execution is completed, etc.

The Callable interface defines methods that need to be implemented for tasks that need to be returned. For example, if the main thread asks a child thread to execute a task, the child thread may be time-consuming. After starting the child thread to execute the task, the main thread will do other things and get the execution results of the child task after a while.

FutureTask

Specification: system out. println(futureTask.get()); Put last

As long as the get method appears, whether the calculation is completed or not, wait for the result to complete before running the following code

Improvements to Future

Advantages of completable future:

When the asynchronous task ends, it will automatically call back the method of an object;

When an asynchronous task makes an error, it will automatically call back the method of an object;

After the callback is set by the main thread, it no longer cares about the execution of asynchronous tasks. Asynchronous tasks can be executed sequentially

Differences between set and get

There is basically no difference between set and get. join does not throw exceptions

Case: sexual price demand of e-commerce websites

Record a highlight of your project:

I have made a price comparison demand, which requires us to climb the data of other websites and look at the price of the same item on different websites. The crawler's brother provided me with some data in JOSN format. I made a HashMap myself (or in the zest of Redis). After data cleaning and ensuring that there are no duplicate data, there are about 10000 pieces of data.

For these 10000 pieces of data, the most stupid method is full-text scanning, one by one, but although this can be achieved, it is relatively slow

Later, I learned that there is a completable future in JUC, which can do asynchronous multithreading concurrency without blocking. After using it, I can optimize the performance of the website from xxx seconds to xxx seconds, which is a highlight I am proud of in technology.

Moreover, completable future uses the forkjoin thread pool by default. I have written the thread pool, threadpollactuator, and the specific parameters are defined according to my own system

package com.juc;

import jdk.nashorn.internal.objects.annotations.Getter;

import java.util.*;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: GengKY
 * @Date: 2021/12/29 13:29
 */

class ComplateFutureNetMallDemo{

    static List<NetMall> list= Arrays.asList(
            new NetMall("JD"),
            new NetMall("PDD"),
            new NetMall("TaoBao"),
            new NetMall("DangDang"),
            new NetMall("TianMao")
    );

    public static List<String> getPriceByStep(List<NetMall> list,String productName){
        return list.stream()
                .map(netMall -> String.format(productName+"in %s price is %.2f",netMall.getMallName(),netMall.calPrice(productName)))
                .collect(Collectors.toList());
    }


    //List<NetMall> ---->List<completableFuture<String>> --->List<string>
    public static List<String> getPriceByASync(List<NetMall> list,String productName){
        return list.stream()
                .map(netMall -> CompletableFuture.supplyAsync(
                        () -> String.format(productName + "in %s price is %.2f", netMall.getMallName(), netMall.calPrice(productName))))
                .collect(Collectors.toList())
                .stream().map(CompletableFuture::join)
                .collect(Collectors.toList());

    }

    public static void main(String[] args) {

        System.out.println("1------------");
        long startTime=System.currentTimeMillis();
        List<String> list1 = getPriceByStep(list, "mysql");
        for (String element : list1) {
            System.out.println(element);
        }
        long endTime=System.currentTimeMillis();
        System.out.println("Spend time: "+(endTime-startTime)+"millisecond");


        System.out.println("2------------");
        long startTime2=System.currentTimeMillis();
        List<String> list2 = getPriceByASync(list, "mysql");
        for (String element : list2) {
            System.out.println(element);
        }
        long endTime2=System.currentTimeMillis();
        System.out.println("Spend time: "+(endTime2-startTime2)+"millisecond");
    }
}

public class NetMall {
    private String mallName;
    public String getMallName() {
        return mallName;
    }
    public NetMall(String mallName){
        this.mallName=mallName;
    }
    public double calPrice(String productName) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
    }
}

Common methods of completable future

Obtain results and trigger calculations

Process the calculation results

Consume the calculation results

Select the calculation speed

Consolidate the calculation results

homework

Thenpose() pipeline

"Lock" in Java

Optimistic lock and pessimistic lock

What is the thread eight lock / lock

According to the scope of the lock:

No lock, with lock;

Lock block and lock method body;

Object lock, class lock

The cases of eight locks are actually reflected in three places:

It acts on the instance method, locks the current instance, and obtains the lock of the current instance before entering the step code

It acts on the code block and locks the objects configured in parentheses

It acts on the static method, locks the current class, and obtains the lock of the current class object before entering the synchronization code

Analyzing sync from the perspective of self code

Sync sync code block

Lock synchronization code block: object locks

public void method(){
    Object object=new Object();
    synchronized (object){
        System.out.println("hello");
    }
}

Lock synchronization code block: class lock

public void method(){
    Object object=new Object();
    synchronized (object.getClass()){
        System.out.println("hello");
    }
}

The synchronized synchronization statement block is implemented using the monitorenter and monitorexit instructions,

The monitorenter instruction points to the start position of the synchronization code block, and the monitorexit instruction indicates the end position of the synchronization code block.

When the monitorenter instruction is executed, the thread attempts to acquire the lock, that is, to acquire the ownership of the object monitor.

In the Java virtual machine (HotSpot), Monitor is implemented based on C + +, which is implemented by ObjectMonitor Implemented. An ObjectMonitor object is built into each object.

In addition, wait/notify and other methods also depend on the monitor object, which is why wait/notify and other methods can only be called in synchronized blocks or methods, otherwise Java. Net will be thrown The reason for the exception of lang.illegalmonitorstateexception.

When you execute monitorenter, you will try to obtain the lock of the object. If the lock counter is 0, it means that the lock can be obtained. After obtaining, set the lock counter to 1, that is, increase 1.

After the monitorexit instruction is executed, set the lock counter to 0, indicating that the lock is released. If the object lock acquisition fails, the current thread will block and wait until the lock is released by another thread.

sync common synchronization method

The synchronized modified method does not have the monitorenter instruction and monitorexit instruction. Instead, ACC is obtained_ Synchronized identification,

This identifier indicates that the method is a synchronous method. The JVM passes this acc_ The synchronized access flag is used to identify whether a method is declared as a synchronous method, so as to execute the corresponding synchronous call.

sync lock static synchronization method

What is the of sync lock

Monitors (English: monitors, also known as monitors) is a program structure in which multiple working threads formed by multiple subroutines (objects or modules) are mutually exclusive to access shared resources.

These shared resources are generally hardware devices or a group of variables. All operations that can be performed on shared variables are concentrated in one module. (the semaphore and its operation primitive are "encapsulated" in an object) the pipeline realizes that at most one thread is executing a subprogram of the pipeline at a point in time. The management process provides a mechanism. The management process can be regarded as a software module. It encapsulates the shared variables and the operations on these shared variables to form a functional module with a certain interface. The process can call the management process to realize process level concurrency control.

MarkWord preheating

synchronized must act on an object, so Java stores lock related information in the object's header file. The lock upgrade function mainly depends on the lock flag bit and release bias lock flag bit in MarkWord. We will deepen the later explanation of lock upgrade. At present, in order to connect the past and the future, we can first mix the following figure with a familiar look, O(∩ ∩) O

Fair lock and unfair lock

class Ticket{
    private int number = 30;
    ReentrantLock lock = new ReentrantLock();

    public void sale(){
        lock.lock();
        try{
            if(number > 0)
                System.out.println(Thread.currentThread().getName()+"Sold article:\t"+(number--)+"\t Remaining:"+number);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}


public class SaleTicketDemo{
    public static void main(String[] args){
        Ticket ticket = new Ticket();
        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"a").start();
        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"b").start();
        new Thread(() -> { for (int i = 0; i <35; i++)  ticket.sale(); },"c").start();
    }
}

Why is there a fair lock / unfair lock design? Why default to unfairness?

1. There is still a time difference between restoring the suspended thread and obtaining the real lock. From the perspective of developers, this time is very small, but from the perspective of CPU, this time difference is still obvious. Therefore, unfair lock can make full use of CPU time slice and minimize CPU idle state time.

2. An important consideration when using multithreading is the overhead of thread switching. When using an unfair lock, when a thread requests the lock to obtain the synchronization state and then releases the synchronization state, it does not need to consider whether there is a precursor node, so the thread that just released the lock has a very high probability of obtaining the synchronization state again at this moment, so the overhead of the thread is reduced.

What's the problem with using fair locks?

Fair locks ensure the fairness of the queue. Unfair locks ignore this rule, so it may lead to a long queue and no chance to obtain the lock,

This is the legendary "lock hunger"

When to use fair? When to use unfair?

For higher throughput, it is obvious that unfair locking is more appropriate, because it saves a lot of thread switching time, and the throughput will naturally increase;

Otherwise, use the fair lock and everyone will use it fairly.

Reentrant lock (recursive lock)

explain:

Reentrant lock, also known as recursive lock

It means that when the same thread acquires a lock in the outer method, the inner method entering the thread will automatically acquire the lock (provided that the lock object must be the same object) and will not be blocked because it has been acquired before and has not been released.

If it is a recursive calling method with synchronized modification, the second entry of the program is blocked by itself. Isn't it a big joke? There is a cocoon.

Therefore, ReentrantLock and synchronized in Java are reentrant locks. One advantage of reentrant locks is that deadlocks can be avoided to a certain extent.

Implicit reentrant lock

It refers to a lock that can be repeated and called recursively. After the lock is used in the outer layer, it can still be used in the inner layer without deadlock. Such a lock is called a reentrant lock.
To put it simply, you can always get a lock when calling other synchronized modified methods or code blocks of this class inside a synchronized modified method or code block

In contrast to reentrant phase locking, non reentrant lock can not be called recursively, and the recursive call will deadlock.

Synchronization block

public class ReEntryLockDemo
{
    public static void main(String[] args){
        final Object objectLockA = new Object();
        new Thread(() -> {
            synchronized (objectLockA){
                System.out.println("-----Outer call");
                synchronized (objectLockA){
                    System.out.println("-----Middle level call");
                    synchronized (objectLockA){
                        System.out.println("-----Inner call");
                    }
                }
            }
        },"a").start();
    }
}

Synchronization method

public class ReEntryLockDemo{
    public synchronized void m1(){
        System.out.println("-----m1");
        m2();
    }
    public synchronized void m2(){
        System.out.println("-----m2");
        m3();
    }
    public synchronized void m3(){
        System.out.println("-----m3");
    }
    public static void main(String[] args){
        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
        reEntryLockDemo.m1();
    }
}

Implementation mechanism of Synchronized reentry

Each lock object has a lock counter and a pointer to the thread holding the lock.

When monitorenter is executed, if the counter of the target lock object is zero, it indicates that it is not held by other threads. Java virtual opportunity sets the holding thread of the lock object as the current thread and increases its counter by 1.

When the counter of the target lock object is not zero, if the holding thread of the lock object is the current thread, the Java virtual machine can increase its counter by 1, otherwise it needs to wait until the holding thread releases the lock.

When monitorexit is executed, the Java virtual machine needs to decrease the counter of the lock object by 1. A zero counter indicates that the lock has been released.

Explicit reentrant lock

If the lock and unlock times are different, you can play by yourself

If multiple threads work together, others' threads will get stuck

public class ReEntryLockDemo{
    static Lock lock = new ReentrantLock();
    public static void main(String[] args){
        new Thread(() -> {
            lock.lock();//1. First lock
            try{
                System.out.println("----Outer call lock");
                lock.lock();//2. Second lock
                try{
                    System.out.println("----Inner call lock");
                }finally {
                    //lock.unlock(); //  Under normal circumstances, the lock must be unlocked several times
                    //Normal operation without jamming
                }
            }finally {
                lock.unlock();
            }
        },"a").start();
    }
}
public class ReEntryLockDemo{
    static Lock lock = new ReentrantLock();
    public static void main(String[] args){
        new Thread(() -> {
            lock.lock();//1. First lock
            try{
                System.out.println("----Outer call lock");
                lock.lock();//2. Second lock
                try{
                    System.out.println("----Inner call lock");
                }finally {
                    // It is intentionally noted here that the number of locks and releases is different
                    // Because the number of locks added and released is different, the second thread is always unable to obtain the lock, resulting in waiting.
                    //lock.unlock(); //  Under normal circumstances, the lock must be unlocked several times
                    //The second thread gets stuck
                }
            }finally {
                lock.unlock();
            }
        },"a").start();

        new Thread(() -> {
            lock.lock();
            try{
                System.out.println("b thread----Outer call lock");
            }finally {
                lock.unlock();
            }
        },"b").start();
    }
}

deadlock

Deadlock refers to the phenomenon that two or more threads wait for each other due to competing for resources during execution. If there is no external interference, they will not be able to move forward. If the system resources are sufficient and the resource requests of the process can be met, the possibility of deadlock is very low, otherwise they will fall into deadlock due to competing for limited resources.

Write a deadlock case

public class DeadLockDemo{
    public static void main(String[] args){
        final Object objectLockA = new Object();
        final Object objectLockB = new Object();

        new Thread(() -> {
            synchronized (objectLockA){
                System.out.println(Thread.currentThread().getName()+"\t"+"Own holding A,Hope to obtain B");
                //Pause the thread for a few seconds
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectLockB){
                    System.out.println(Thread.currentThread().getName()+"\t"+"A-------Already obtained B");
                }
            }
        },"A").start();

        new Thread(() -> {
            synchronized (objectLockB){
                System.out.println(Thread.currentThread().getName()+"\t"+"Own holding B,Hope to obtain A");
                //Pause the thread for a few seconds
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectLockA){
                    System.out.println(Thread.currentThread().getName()+"\t"+"B-------Already obtained A");
                }
            }
        },"B").start();
    }
}

How to prove deadlock

Call up the console using the jconsole command

Using jps -l jstack thread ID

Some other locks

Thread interrupt mechanism

Interview question feedback

What is an interrupt

Method of API related to interrupt

How to stop a thread using the interrupt flag

Through a volatile variable

Through AtomicBoolean

It is implemented through the interrupt api method of Thread class

interrupt source code analysis

If a blocked thread (wait(), join(), sleep()) is an interrupt, the interrupt flag bit will be cleared (reset to false) and an InterruptedException exception will be thrown

isInterrupted source code analysis

Does Interrupt stop immediately

The following code shows that when Interrupt is called on a thread, it does not stop immediately

Backhand case

Second hand case-1

Execution results:

The program is executed normally, and the hello interrupt is printed continuously within 3 seconds. After 3 seconds, the "* * * program end" is printed

Second hand case-2

Compared with "subsequent case-1", perform sleep operation in the while loop and catch exceptions with try catch

Guess: print "hello interrupt" six times, and print "* * * program end"

Operation results:

An exception is reported and the program cannot be stopped

Cause of problem:

When thread t1 is sleeping, thread t2 sets t1 Interrupt, resulting in an exception

After an exception is reported, the interrupt state is reset to false and cannot be stopped

Solution:

Set interrupt once in try catch

Second hand case-3

conclusion

Static method thread interrupted

Code demonstration

Method description

Interrupted vs IsInterrupted

Thread interrupt summary

Understand the system architecture design

LockSupport

What is LockSupport

LockSupport Java doc

LockSupport is the basic thread blocking primitive used to create locks and other synchronization classes.

Brief description

One sentence to explain LockSupport:

LockSupport is an enhanced version of the thread wait / notify mechanism

park() and unpark() in LockSupport are used to block threads and unblock threads respectively.

In short, it is stronger than wait/notify and await/signal.

Three ways to make threads wait and wake up

  • Method 1: use the wait() method in object to make the thread wait, and use the notify() method in object to wake up the thread

  • Method 2: use the await() method of Condition in the JUC package to make the thread wait, and use the signal() method to wake up the thread

  • Method 3: LockSupport class can block the current thread and wake up the specified blocked thread

Two important methods

Thread waiting wake-up mechanism

Wait and Notify restrictions

The wait and notify methods in the Object class implement thread waiting and wake-up

public class WaitNotifyDemo {

	static Object lock = new Object();
	
	public static void main(String[] args) {
		new Thread(()->{
			synchronized (lock) {
				System.out.println(Thread.currentThread().getName()+" come in.");
				try {
					lock.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+" Wake up.");
		}, "Thread A").start();
		
		new Thread(()->{
			synchronized (lock) {
				lock.notify();
				System.out.println(Thread.currentThread().getName()+" notice.");
			}
		}, "Thread B").start();
	}
}

The wait and notify methods must be used in the synchronization block or method in pairs, otherwise they will throw Java lang.IllegalMonitorStateException.

public class WaitNotifyDemo {
    static Object lock = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " come in.");
            try {
                lock.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Wake up.");
        }, "Thread A").start();

        new Thread(() -> {
            lock.notify();
            System.out.println(Thread.currentThread().getName() + " notice.");
        }, "Thread B").start();
    }
}
Exception in thread "Thread B" java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.notify(Native Method)
	at com.example.demo07.main.WaitNotifyDemo.lambda$main$1(WaitNotifyDemo.java:19)
	at java.base/java.lang.Thread.run(Thread.java:829)
Thread A come in.
Thread A Wake up.
java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.wait(Native Method)
	at java.base/java.lang.Object.wait(Object.java:328)
	at com.example.demo07.main.WaitNotifyDemo.lambda$main$0(WaitNotifyDemo.java:11)
	at java.base/java.lang.Thread.run(Thread.java:829)

The call sequence should wait before notify before OK.

wait first and then notify and notifyall methods. The waiting thread will be awakened, otherwise it cannot be awakened

public class WaitNotifyDemo {
	static Object lock = new Object();
	public static void main(String[] args) {
		new Thread(()->{
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (lock) {
				System.out.println(Thread.currentThread().getName()+" come in.");
				try {
					lock.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+" Wake up.");
		}, "Thread A").start();

		new Thread(()->{
			synchronized (lock) {
				lock.notify();
				System.out.println(Thread.currentThread().getName()+" notice.");
			}
		}, "Thread B").start();
	}
}
Thread B notice.
Thread A come in.
.......Infinite waiting......

Wait and Signal restrictions

The signal method after await in the Condition interface implements thread waiting and wake-up, which is similar to the wait and notify methods in the Object class.

public class ConditionAwaitSignalDemo {		
	public static void main(String[] args) {	
		ReentrantLock lock = new ReentrantLock();
		Condition condition = lock.newCondition();
        
		new Thread(()->{	
			try {
				System.out.println(Thread.currentThread().getName()+" come in.");
				lock.lock();
				condition.await();				
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
			System.out.println(Thread.currentThread().getName()+" Wake up.");
		},"Thread A").start();
		
		new Thread(()->{
			try {
				lock.lock();
				condition.signal();
				System.out.println(Thread.currentThread().getName()+" notice.");
			}finally {
				lock.unlock();
			}
		},"Thread B").start();
	}
}

Output results:

Thread A come in.
Thread B notice.
Thread A Wake up.

await and signal methods must be used in a synchronization block or method in pairs, or Java. Net will be thrown lang.IllegalMonitorStateException.

The call sequence should be await before signal is OK.

Introduction to LockSupport method

LockSupport method summary in API

Traditional synchronized and Lock implement the constraint of waiting for wake-up notification

  • To obtain and hold a lock, a thread must be in a lock block (synchronized or lock)
  • You must wait before waking up before the thread can be awakened

park wait and unpark wake in LockSupport class

Basic thread blocking primitives for creating locks and other synchronization classes.

This class associates, with each thread that uses it, a permit (in the sense of the Semaphore class). A call to park will return immediately if the permit is available, consuming it in the process; otherwise it may block. A call to unpark makes the permit available, if it was not already available. (Unlike with Semaphores though, permits do not accumulate. There is at most one.) link

LockSupport is the basic thread blocking primitive used to create locks and other synchronization classes.

LockSupport class uses a concept called permission to block and wake up threads. Each thread has a permission. Permission has only two values: 1 and zero. The default is zero.

You can think of a license as a (0.1) Semaphore, but unlike Semaphore, the cumulative upper limit of the license is 1.

The operation of blocking and waking up threads is realized through park() and unpark(thread) methods

park()/park(Object blocker) - block the current thread and block the incoming specific thread

public class LockSupport {
    ...
    public static void park() {
        UNSAFE.park(false, 0L);
    }
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    ...
}

The default permission is 0, so when you call the park() method at the beginning, the current thread will block. When another thread sets the permission of the current thread to 1, the park method will wake up, and then set the permission to 0 again and return.

unpark(Thread thread) - wakes up the specified thread that is blocked

public class LockSupport {
    ...
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    ...
}

After the unpark(thread) method is called, the permission permission of the thread will be set to 1 (note that if the unpark method is called multiple times, it will not accumulate, and the pemit value is still 1) and the thead thread will wake up automatically, that is, locksupport in the previous blocking The park () method returns immediately.

LockSupport case analysis

Normal use

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread a = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " Wake up. " + System.currentTimeMillis());
        }, "Thread A");
        a.start();

        Thread b = new Thread(()->{
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+" notice.");
        }, "Thread B");
        b.start();
    }
}

Output results:

Thread A come in.
Thread B notice.
Thread A Wake up.

Normal + no lock block requirements.

LockSupport can ignore the previously incorrect wake first and wait sequence.

[support] first release the lock, unpark, and then add the lock

Why can I wake up the thread first and then block the thread?

Because unpark obtains a voucher and then calls the park method, you can consume the voucher in a proper way, so it will not be blocked.

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread a = new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " Wake up. " + System.currentTimeMillis());
        }, "Thread A");
        a.start();

        Thread b = new Thread(()->{
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+" notice.");
        }, "Thread B");
        b.start();
    }
}

[support] unpack multiple times

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread a = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " Wake up. " + System.currentTimeMillis());
        }, "Thread A");
        a.start();

        Thread b = new Thread(()->{
            LockSupport.unpark(a);
            LockSupport.unpark(a);
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+" notice.");
        }, "Thread B");
        b.start();
    }
}

[not supported] pack multiple times, resulting in infinite waiting

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread a = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " Wake up. " + System.currentTimeMillis());
        }, "Thread A");
        a.start();

        Thread b = new Thread(()->{
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+" notice.");
        }, "Thread B");
        b.start();
    }
}

[support] multiple passes provided by different threads

package com.juc;

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo2 {
    public static void main(String[] args) {
        //park multiple times
        Thread thread1  = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " come in. " + System.currentTimeMillis());
            LockSupport.park();
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " awaken. " + System.currentTimeMillis());
        }, "Thread A");
        thread1.start();

        //Thread B unpark once
        new Thread(()->{
            LockSupport.unpark(thread1);
            System.out.println(Thread.currentThread().getName()+" notice.");
        }, "Thread B").start();

        //Thread C unpark once
        new Thread(()->{
            LockSupport.unpark(thread1);
            System.out.println(Thread.currentThread().getName()+" notice.");
        }, "Thread C").start();

        //Thread D unpark once
        new Thread(()->{
            LockSupport.unpark(thread1);
            System.out.println(Thread.currentThread().getName()+" notice.");
        }, "Thread D").start();
    }
}

Key notes

LockSupport is the basic thread blocking primitive used to create lock and other synchronization classes.

LockSuport is a thread blocking tool class. All methods are static methods. Threads can be blocked at any position. After blocking, there are also corresponding wake-up methods. In the final analysis, LockSupport calls the native code in Unsafe.

LockSupport provides park() and unpark() methods to block threads and unblock threads

LockSupport has a permission association with each thread that uses it. permit is equivalent to the switch of 1 and 0. The default is 0,

When you call unpark once, add 1 to become 1,

Calling park once will consume permit, that is, change 1 to 0, and park will return immediately.

If you call park again, it will become blocked (because if the permission is zero, it will be blocked here until the permission becomes 1). At this time, calling unpark will set the permission to 1. Each thread has a related permission. There is only one permission at most. Repeated calls to unpark will not accumulate credentials.

Image understanding

Thread blocking requires the consumption of a permit, which is only 1 at most.

When the park method is called

  • If there is a voucher, it will be consumed directly and then exit normally.
  • If there is no certificate, you must block and wait for the certificate to be available.

On the contrary, unpark will add one voucher, but there can only be one voucher at most, which is accumulated without release.

Interview questions

1. Why can I wake up the thread first and then block the thread?

Because unpark obtains a voucher and then calls the park method, you can consume the voucher in a proper way, so it will not be blocked.

2. Why block twice after waking up twice, but the end result will block the thread?

Because the maximum number of vouchers is 1 (cannot be accumulated), calling unpark twice in a row has the same effect as calling unpark once, and only one voucher will be added; However, calling park twice needs to consume two vouchers, which are not enough and cannot be released.

Java memory model value JMM

Interview question feedback

Computer hardware architecture

What is JMM

Three features under JVM specification

visibility

Atomicity

It means that an operation is non interruptible, that is, in a multithreaded environment, the operation cannot be disturbed by other threads

Order

Multi thread traversal reading process

Read process

Small summary

All shared variables we define are stored in physical main memory

Each thread has its own independent working memory, which stores a copy of the variable used by the thread (a copy of the variable in the main memory)

All operations of a thread on shared variables must be carried out in the thread's own working memory first and then written back to the main memory. You can't read or write directly from the main memory (you can't skip levels)

Variables in the working memory of other threads cannot be accessed directly between different threads. Variable values between threads need to be transferred through the main memory (peers cannot access each other)

Happens before principle

x. y case description

Description of prior occurrence principle

Happens before eight rules

Code description

Memory barrier

volatile memory semantics

What is a memory barrier

Memory barrier (also known as memory barrier, memory barrier, barrier instruction, etc.) is a kind of synchronization barrier instruction, which is a synchronization point in the operation of random access to memory by CPU or compiler, so that all read and write operations before this point can be executed before the operation after this point can be started), so as to avoid code reordering. Memory barrier is actually a kind of JVM instruction. The rearrangement rules of Java memory model will require the Java compiler to insert specific memory barrier instructions when generating JVM instructions. Through these memory barrier instructions, volatile realizes the visibility and order in Java memory model, but volatile cannot guarantee atomicity.

All write operations before the memory barrier must be written back to the main memory,

All read operations after the memory barrier can obtain the latest results of all write operations before the memory barrier (visibility is achieved).

Source code analysis of four types of memory barriers

What do the four barriers mean

Memory barrier insertion strategy

volatile variable rule of happens before

Write a summary

1. Insert a StoreStore barrier in front of each volatile write operation

2. Insert a StoreLoad barrier after each volatile write operation

Reading summary

3. Insert a LoadLoad barrier after each volatile read operation

4. Insert a LoadStore barrier after each volatile read operation

volatile keyword

Ensure visibility

Code description

Without volatile and visibility, the program cannot stop

volatile is added to ensure visibility, and the program can stop

Reading and writing process of volatile variable

  • Lock (lock) acts on variables in main memory and identifies variables as thread exclusive state.
  • Read is a variable that acts on the main memory and transfers the value of the variable from the main memory to the working memory of the thread for the next load operation.
  • Load, the variable that acts on the working memory. Put the read operation main memory variable into the variable copy of the working memory.
  • Use (use), a variable that acts on the working memory and transfers the variable in the working memory to the execution engine. This operation will be executed whenever the virtual machine encounters a bytecode instruction that needs to use the value of the variable.
  • Assign is a variable that acts on the working memory. It assigns a value received from the execution engine to the variable copy of the working memory. This operation will be executed whenever the virtual machine encounters a bytecode instruction that assigns a value to the variable.
  • Store, a variable that acts on the working memory. It transfers the value of a variable from the working memory to the main memory for subsequent write.
  • Write: acts on a variable in main memory. It puts the value of the variable obtained by the store operation from working memory into the variable in main memory.
  • Unlock: a variable that acts on the main memory. It releases a locked variable, and the released variable can be locked by other threads.

No atomicity

Code demonstration

Output result: basically, it will be less than 10000, and 10000 may occur once

Bytecode angle description

Atomicity is not guaranteed

Reading a common variable

volatile processing of instructions

Read and assign a volatile variable

Different non atomic operations during the gap period

Prohibit instruction rearrangement

Description and case

Memory barrier secondary review

The prohibition of instruction rearrangement related to volatile

Code description

Use volatile correctly

Use case: status flag

Use case: read more and write less

Use case: two terminal retrieval

Problem code

Look at the problem code under single thread and multi thread

Double ended retrieval of correct code

The static inner class implements the singleton pattern

volatile summary

What is the memory barrier

Memory barrier:

Is a barrier instruction that causes the CPU or compiler to perform a sort constraint on memory operations issued before and after the barrier instruction. Also called memory fence or fence instruction

Memory barrier four instructions

Insert a StoreStore barrier before each volatile write operation

Insert a StoreLoad barrier after each volatile write operation

Insert a LoadLoad barrier after each volatile read operation

Insert a LoadStore barrier after each volatile read operation

volatile keyword system bottom layer

Bytecode level

keyword

volatile visibility

volatile forbidden rearrangement

Write instruction

Read instruction

Compare Lock's understanding

One sentence summary

CAS

Before CSA

Ensuring thread safety in multithreading environment

What is CAS

Description and principle

Hardware level assurance

CASDemo code

compareAndSet source code

Atomic reference

Handwritten spin lock

Handwritten spin lock

What is spin lock

ABA problem

Disadvantages of CAS: CPU idling

ABA problem

Code: ABA problem

Code: solve ABA problem

Postmarked atomic reference: AtomicStampedReference

atomicStampedReference.compareAndSet(x,y,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);

use:

Atomic operation class

What's the difference between red and black?

Why use LongAdder instead of AtomicLong?

Basic type atomic class

countDownLatch, where do you use it?

class MyNumber{
    @Getter
    private AtomicInteger atomicInteger = new AtomicInteger();
    public void addPlusPlus(){
        atomicInteger.incrementAndGet();
    }
}


public class AtomicIntegerDemo{
    public static void main(String[] args) throws InterruptedException{
        
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(100);

        for (int i = 1; i <=100; i++) {
            new Thread(() -> {
                try{
                    for (int j = 1; j <=5000; j++){
                        myNumber.addPlusPlus();
                    }
                }finally {
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//Without this line of code, because the main thread is too fast, at the end of main, atomicinteger Incrementandget() hasn't reached 500000 yet
        System.out.println(myNumber.getAtomicInteger().get());
    }
}

Array type atomic class

public class AtomicIntegerArrayDemo{
    public static void main(String[] args){
        
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
        //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
        //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});

        for (int i = 0; i <atomicIntegerArray.length(); i++) {
            System.out.println(atomicIntegerArray.get(i));
        }
        System.out.println();
        System.out.println();
        System.out.println();
        int tmpInt = 0;

        tmpInt = atomicIntegerArray.getAndSet(0,1122);
        System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
        atomicIntegerArray.getAndIncrement(1);
        atomicIntegerArray.getAndIncrement(1);
        tmpInt = atomicIntegerArray.getAndIncrement(1);
        System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1));
    }
}

Reference type atomic class

AtomicReference

@Getter
@ToString
@AllArgsConstructor
class User{
    String userName;
    int    age;
}


public class AtomicReferenceDemo{
    public static void main(String[] args){
        User z3 = new User("z3",24);
        User li4 = new User("li4",26);

        AtomicReference<User> atomicReferenceUser = new AtomicReference<>();

        atomicReferenceUser.set(z3);
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
    }
}

Handwritten spin lock

package com.atguigu.Interview.study.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @auther zzyy
 * @create 2018-12-28 17:57
 * Title: implementing a spin lock
 * Spin lock benefits: loop comparison acquisition has no blocking like wait.
 *
 * The spin lock is completed through CAS operation. Thread A first calls the myLock method and holds the lock for 5 seconds. Then, thread B comes in and finds out
 * Currently, A thread holds A lock, which is not null, so it can only wait by spin until A releases the lock and B then grabs it.
 */
public class SpinLockDemo{
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t come in");
        while(!atomicReference.compareAndSet(null,thread)){

        }
    }

    public void myUnLock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t myUnLock over");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock();
            //Pause the thread for a moment
            try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnLock();
        },"A").start();
        //Pause the thread for A while to ensure that thread A starts and completes before thread B
        try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            spinLockDemo.myLock();
            spinLockDemo.myUnLock();
        },"B").start();

    }
}

AtomicStampedReference

Solving ABA problems

public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args)
    {
        abaProblem();
        abaResolve();
    }

    public static void abaResolve()
    {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t3 ----1st time stamp  "+stamp);
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
            System.out.println("t3 ----2nd time stamp  "+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("t3 ----3rd time stamp  "+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t4 ----1st time stamp  "+stamp);
            //Pause the thread for a few seconds
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }

    public static void abaProblem()
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            atomicInteger.compareAndSet(100,20210308);
            System.out.println(atomicInteger.get());
        },"t2").start();
    }
}

AtomicMarkableReference

It is not recommended to solve ABA problems

It is used to solve the one-time problem and is not suitable for reuse

package com.atguigu.juc.senior.inner.atomic;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @auther zzyy
 * @create 2020-05-23 10:56
 */
public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
    static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false);

    public static void main(String[] args)
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
            System.out.println(Thread.currentThread().getName()+"\t"+"update ok");
        },"t1").start();

        new Thread(() -> {
            //Pause the thread for a few seconds
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicInteger.compareAndSet(100,2020);
        },"t2").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(atomicInteger.get());

        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("============Here is ABA Problem solving,Let's know how many times the reference variable has been changed in the middle=========================");
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+stampedReference.getStamp());
            //Deliberately pause for 200 milliseconds to let the t4 thread get the same version number as t3
            try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }

            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 2 Minor version number"+stampedReference.getStamp());
            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 3 Minor version number"+stampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t =======1 Minor version number"+stamp);
            //Pause for 2 seconds and let t3 complete the ABA operation first to see if you can modify it
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean b = stampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t=======2 Minor version number"+stampedReference.getStamp()+"\t"+stampedReference.getReference());
        },"t4").start();

        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("============AtomicMarkableReference I don't care how many times the reference variable has been changed, but only whether it has been changed======================");

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+marked);
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            markableReference.compareAndSet(100,101,marked,!marked);
            System.out.println(Thread.currentThread().getName()+"\t 2 Minor version number"+markableReference.isMarked());
            markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked());
            System.out.println(Thread.currentThread().getName()+"\t 3 Minor version number"+markableReference.isMarked());
        },"t5").start();

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+marked);
            //Pause the thread for a few seconds
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            markableReference.compareAndSet(100,2020,marked,!marked);
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked());
        },"t6").start();
    }
}

Object to modify the atomic class

Manipulate certain fields within a non thread safe object in a thread safe manner

AtomicIntegerFieldUpdater

class BankAccount{
    private String bankName = "CCB";//bank
    public volatile int money = 0;//Amount of money
    AtomicIntegerFieldUpdater<BankAccount> accountAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");

    //No lock + high performance, local minimally invasive
    public void transferMoney(BankAccount bankAccount){
        accountAtomicIntegerFieldUpdater.incrementAndGet(bankAccount);
    }
}

/**
 * @auther zzyy
 * @create 2020-07-14 18:06
 * Manipulate certain fields of non thread safe objects in a thread safe manner.
 * Requirements:
 * 1000 If an individual transfers one yuan to an account at the same time, the cumulative amount should be increased by 1000 yuan,
 * In addition to synchronized and CAS, it can also be implemented using AtomicIntegerFieldUpdater.
 */
public class AtomicIntegerFieldUpdaterDemo{

    public static void main(String[] args){
        
        BankAccount bankAccount = new BankAccount();

        for (int i = 1; i <=1000; i++) {
            int finalI = i;
            new Thread(() -> {
                bankAccount.transferMoney(bankAccount);
            },String.valueOf(i)).start();
        }

        //Pause milliseconds
        try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(bankAccount.money);
    }
}

AtomicReferenceField

class MyVar{
    public volatile Boolean isInit = Boolean.FALSE;
    AtomicReferenceFieldUpdater<MyVar,Boolean> atomicReferenceFieldUpdater = 	AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");

    public void init(MyVar myVar){
        if(atomicReferenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE)){
            System.out.println(Thread.currentThread().getName()+"\t"+"---init.....");
            //Pause the thread for a few seconds
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"---init.....over");
        }else{
            System.out.println(Thread.currentThread().getName()+"\t"+"------Another thread is initializing");
        }
    }
}


/**
 * @auther zzyy
 * @create 2021-03-18 17:20
 * Multithreading calls the initialization method of a class concurrently. If it has not been initialized, it will perform initialization. It is required that it can only be initialized once
 */
public class AtomicIntegerFieldUpdaterDemo{
    public static void main(String[] args) throws InterruptedException{
        MyVar myVar = new MyVar();

        for (int i = 1; i <=5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            },String.valueOf(i)).start();
        }
    }
}

Deep analysis of atomic operation enhancement principle

LongAdder

Basic use

Ali is dying

Common API

Use case

The LongAccumulator provides custom function operations

A long type aggregator needs to pass in a long type binary operation, which can be used to calculate various aggregation operations, including addition and multiplication

public class LongAccumulatorDemo
{

    LongAdder longAdder = new LongAdder();
    public void add_LongAdder()
    {
        longAdder.increment();
    }

    //LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y,0);
    LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator()
    {
        @Override
        public long applyAsLong(long left, long right)
        {
            return left - right;
        }
    },777);

    public void add_LongAccumulator()
    {
        longAccumulator.accumulate(1);
    }

    public static void main(String[] args)
    {
        LongAccumulatorDemo demo = new LongAccumulatorDemo();

        demo.add_LongAccumulator();
        demo.add_LongAccumulator();
        System.out.println(demo.longAccumulator.longValue());
    }
}

LongAdderAPIDemo

 
package com.atguigu.juc.atomics;

import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

/**
 * @auther zzyy
 * @create 2021-03-19 15:59
 */
public class LongAdderAPIDemo
{
    public static void main(String[] args)
    {
        LongAdder longAdder = new LongAdder();

        longAdder.increment();
        longAdder.increment();
        longAdder.increment();

        System.out.println(longAdder.longValue());

        LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x * y,2);

        longAccumulator.accumulate(1);
        longAccumulator.accumulate(2);
        longAccumulator.accumulate(3);

        System.out.println(longAccumulator.longValue());

    }
}

performance testing

1. Define five methods

2. Define test method

3. Test results

Why is LongAdder fast

LongAdder source code analysis

It's too difficult. I haven't seen it yet. I'll wait for the later supplement [December 31, 2021]

Unsafe class

What is the UnSafe class

Is atomic shaping safe

getAndIncrement source code

Bottom assembly

ThreadLocal

Introduction to Threadlocal

What is / can do / API

ThreadLocal provides thread local variables. These variables are different from normal variables, because each thread has its own independently initialized variable copy when accessing the ThreadLocal instance (through its get or set method).

A ThreadLocal instance is usually a private static field in a class that is used to associate a state (for example, user ID or transaction ID) with a thread.

Each thread has its own copy of local variables (it doesn't bother others to use its own variables and doesn't share them with others. Everyone has a copy and everyone has a copy),

It mainly solves the problem of allowing each thread to bind its own value. By using the get() and set() methods, it can obtain the default value or change its value to the value of the copy saved by the current thread, so as to avoid the thread safety problem.

Case: house purchase commission

ThreadLocal initialization method

Housing Commission case

Existing problems

The custom ThreadLocal variable must be recycled. Especially in the thread pool scenario, threads are often reused,

If the custom ThreadLocal variable is not cleared, subsequent business logic may be affected and memory leakage may be caused.

Try to recycle with try finally blocks in the proxy.

Start with ALI ThreadLocal specification

Non thread safe SimpleDateFormat

Official documents

Error code demonstration

Problems arising

Source code analysis conclusion

Solution

Option 1:

        for (int i = 1; i <=30; i++) {
            new Thread(() -> {
                try {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//Every time you use new
                    System.out.println(sdf.parse("2020-11-11 11:11:11"));
                    sdf = null;//Help GC recycle
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

Option 2:

code

    private static final ThreadLocal<SimpleDateFormat>  sdf_threadLocal =
            ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

This solution is recommended by Ali

Other options:

code

DataUtils

package com.atguigu.juc.senior.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @auther zzyy
 * @create 2020-05-03 10:14
 */
public class DateUtils
{
    /*
    1   SimpleDateFormat If multithreading sharing is a thread unsafe class
    public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String format(Date date)
    {
        return SIMPLE_DATE_FORMAT.format(date);
    }

    public static Date parse(String datetime) throws ParseException
    {
        return SIMPLE_DATE_FORMAT.parse(datetime);
    }*/

    //2 ThreadLocal can ensure that each thread can get its own SimpleDateFormat object, so there will be no competition.
    public static final ThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static String format(Date date)
    {
        return SIMPLE_DATE_FORMAT_THREAD_LOCAL.get().format(date);
    }

    public static Date parse(String datetime) throws ParseException
    {
        return SIMPLE_DATE_FORMAT_THREAD_LOCAL.get().parse(datetime);
    }


    //3 DateTimeFormatter replaces SimpleDateFormat
    /*public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static String format(LocalDateTime localDateTime)
    {
        return DATE_TIME_FORMAT.format(localDateTime);
    }

    public static LocalDateTime parse(String dateString)
    {

        return LocalDateTime.parse(dateString,DATE_TIME_FORMAT);
    }*/
}

homework

From: Ali manual

    //3 DateTimeFormatter replaces SimpleDateFormat
    /*public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static String format(LocalDateTime localDateTime)
    {
        return DATE_TIME_FORMAT.format(localDateTime);
    }

    public static LocalDateTime parse(String dateString)
    {

        return LocalDateTime.parse(dateString,DATE_TIME_FORMAT);
    }*/

ThreadLocal source code analysis

ThreadLocalMap

threadLocalMap is actually an Entry object with threadLocal instance as key and any object as value.

When we assign a value to the threadLocal variable, we actually store the Entry with the current threadLocal instance as the key and value into the threadLocalMap

ThreadLocal memory leak

Existing problems:

In the thread pool environment, threads are often reused

What is a memory leak

The memory occupied by objects or variables that will no longer be used cannot be recycled, which is a memory leak.

Look at ThreadLocalMap again

ThreadLocalMap literally shows that this is a map that stores ThreadLocal objects (in fact, it is used as the Key), but ThreadLocal objects are packaged in two layers:

(1) The first layer of packaging uses WeakReference < threadloca <? > > Change the ThreadLocal object into a weakly referenced object;

(2) The second layer of packaging defines a special class Entry to extend WeakReference < ThreadLocal <? > >

Overall architecture

Four references of strong, weak and virtual

When the memory is insufficient, the JVM starts garbage collection. For strongly referenced objects, even if there is an OOM, the object will not be recycled and will not be collected.

Strong reference is our most common common object reference. As long as there is a strong reference pointing to an object, it can indicate that the object is still "alive", and the garbage collector will not touch this object. The most common in Java is strong reference. Assigning an object to a reference variable is a strong reference. When an object is referenced by a strongly referenced variable, it is in reachable state. It cannot be recycled by the garbage collection mechanism. Even if the object will never be used in the future, the JVM will not recycle it. Therefore, strong references are one of the main causes of JAVA memory leaks.

For an ordinary object, if there is no other reference relationship, as long as the scope of the reference is exceeded or the corresponding (strong) reference is explicitly assigned null,
It is generally considered that it can be garbage collected (of course, the specific recycling time depends on the garbage collection strategy).

 public static void strongReference(){
    MyObject myObject = new MyObject();
    System.out.println("-----gc before: "+myObject);

    myObject = null;
    System.gc();
    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

    System.out.println("-----gc after: "+myObject);
}

Soft reference is a relatively strong reference, which weakens some references and needs to be used in Java Implemented by lang.ref.softreference class, the object can be exempted from some garbage collection.

For objects with only soft references,

When the system memory is sufficient, it will not be recycled,

When the system runs out of memory, it is recycled.

Soft references are usually used in memory sensitive programs. For example, the cache is useful for soft references. When the memory is enough, they are retained and recycled if not enough!

class MyObject{
    //You don't need to call this method in general development. This time is just for lecture demonstration
    @Override
    protected void finalize() throws Throwable{
        System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked....");
    }
}

/**
 * @auther zzyy
 * @create 2021-03-24 10:31
 */
public class ReferenceDemo{
    public static void main(String[] args){
        //When our memory is not enough, soft will be recycled. Set our memory size: - Xms10m -Xmx10m
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-----gc after Enough memory: "+softReference.get());

        try
        {
            byte[] bytes = new byte[9 * 1024 * 1024];
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after insufficient memory: "+softReference.get());
        }
    }

    public static void strongReference(){
        MyObject myObject = new MyObject();
        System.out.println("-----gc before: "+myObject);

        myObject = null;
        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after: "+myObject);
    }
}

Weak references require Java Lang.ref.weakreference class, which has a shorter lifetime than soft reference,

For objects with only weak references, as soon as the garbage collection mechanism runs, the memory occupied by the object will be reclaimed regardless of whether the memory space of the JVM is sufficient.

class MyObject
{
    //You don't need to call this method in general development. This time is just for lecture demonstration
    @Override
    protected void finalize() throws Throwable
    {
        System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked....");
    }
}

/**
 * @auther zzyy
 * @create 2021-03-24 10:31
 */
public class ReferenceDemo
{
    public static void main(String[] args)
    {
        WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
        System.out.println("-----gc before Enough memory: "+weakReference.get());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after Enough memory: "+weakReference.get());
    }

    public static void softReference()
    {
        //When our memory is not enough, soft will be recycled. Set our memory size: - Xms10m -Xmx10m
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-----gc after Enough memory: "+softReference.get());

        try
        {
            byte[] bytes = new byte[9 * 1024 * 1024];
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after insufficient memory: "+softReference.get());
        }
    }

    public static void strongReference()
    {
        MyObject myObject = new MyObject();
        System.out.println("-----gc before: "+myObject);

        myObject = null;
        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after: "+myObject);
    }
}

Virtual reference requires Java Lang.ref.phantomreference class.

As the name suggests, it is virtual. Unlike other references, virtual references do not determine the life cycle of objects.
If an object holds only virtual references, it may be recycled by the garbage collector at any time as if it had no references,
It cannot be used alone or access objects through it. Virtual references must be used in conjunction with reference queues.

The main function of virtual reference is to track the state of the object being garbage collected. It only provides a mechanism to ensure that the object does something after it is finalize d. The get method of phantom reference always returns null, so the corresponding reference object cannot be accessed.

Its significance is that an object has entered the finalization stage and can be recycled by gc to realize more flexible recycling operations than the finalization mechanism.

In other words, the only purpose of setting virtual reference association is to receive a system notification or add further processing when the object is recycled by the collector.

class MyObject
{
    //You don't need to call this method in general development. This time is just for lecture demonstration
    @Override
    protected void finalize() throws Throwable
    {
        System.out.println(Thread.currentThread().getName()+"\t"+"---finalize method invoked....");
    }
}

/**
 * @auther zzyy
 * @create 2021-03-24 10:31
 */
public class ReferenceDemo
{
    public static void main(String[] args)
    {
        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue();
        PhantomReference<MyObject> phantomReference = new PhantomReference<>(new MyObject(),referenceQueue);
        //System.out.println(phantomReference.get());

        List<byte[]> list = new ArrayList<>();

        new Thread(() -> {
            while (true)
            {
                list.add(new byte[1 * 1024 * 1024]);
                try { TimeUnit.MILLISECONDS.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(phantomReference.get());
            }
        },"t1").start();

        new Thread(() -> {
            while (true)
            {
                Reference<? extends MyObject> reference = referenceQueue.poll();
                if (reference != null) {
                    System.out.println("***********A virtual object has been added to the queue");
                }
            }
        },"t2").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
    }

    public static void weakReference()
    {
        WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
        System.out.println("-----gc before Enough memory: "+weakReference.get());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after Enough memory: "+weakReference.get());
    }

    public static void softReference()
    {
        //When our memory is not enough, soft will be recycled. Set our memory size: - Xms10m -Xmx10m
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-----gc after Enough memory: "+softReference.get());

        try
        {
            byte[] bytes = new byte[9 * 1024 * 1024];
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after insufficient memory: "+softReference.get());
        }
    }

    public static void strongReference()
    {
        MyObject myObject = new MyObject();
        System.out.println("-----gc before: "+myObject);

        myObject = null;
        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after: "+myObject);
    }
}

Why use weak references

Conclusion:

From the previous set, getentry and remove methods, we can see that in the life cycle of threadLocal, for the problem of memory leakage in threadLocal,

Clean up the dirty entry with null key through three methods: expungeStaleEntry, cleansomeslots and replacestateentry.

Weak citation conclusion

Best practices

Java object memory layout and object header

Interview question feedback

Object object = new Object()

Talk about your understanding of this sentence? Generally speaking, by default, how much memory space does a new object occupy in JDK8

Object's memory layout

In the HotSpot virtual machine, the storage layout of objects in heap memory can be divided into three parts:

An object instance: object header, instance data, and Padding

Object header

Object header = object tag (MarkWord) + class meta information (type pointer)

MarkWord is designed as a non fixed data structure

In order to store as much data as possible in a very small memory space. It will reuse its own storage space according to the state of the object, that is, the data stored in MarkWord will change with the change of lock flag bit during operation.

Default storage: HashCode, generation age, lock flag bit and other information of the object.

Object tag (MarkWord)

What does it hold?

The HashCode, generation age, lock flag bit and other information of the object are stored by default.

This information is independent of the definition of the object itself, so MarkWord is designed as a non fixed data structure

In order to store as much data as possible in a very small memory space. It will reuse its own storage space according to the state of the object, that is, the data stored in MarkWord will change with the change of lock flag bit during operation.

Class meta information (type pointer)

Object points to its class metadata. The virtual machine uses this pointer to determine which class instance this object is.

Refer to the original drawing in the JVM course of Mr. Song Hongkang

How big is the object header

In the 64 bit system, Mark Word occupies 8 bytes and type pointer occupies 8 bytes, a total of 16 bytes.

Instance data

Store the attribute (Field) data information of the class, including the attribute information of the parent class,

If the instance part of the array also includes the length of the array, this part of memory is aligned by 4 bytes.

class MyObject{
	int i=5;
	char a='a';
}

MyObjec Object first 16 bytes: MarkWord 8 Bytes + Type pointer 8 bytes
int Type: 32 bit 3 Bytes
char Type: 16 bit 2 Bytes
 Total: 16+3+2=21 Bytes
 Attribute padding: 3 bytes
 Total: 24 bytes

Align fill

The virtual machine requires that the starting address of the object must be an integer multiple of 8 bytes.

The filling data does not have to exist, but only for byte alignment. This part of memory is supplemented and aligned by 8 bytes.

MarkWord object header

Official website theory

Again, object header MarkWord

32-bit virtual machine

64 bit virtual machine

64 bit virtual machine source code

Object layout description and compressed pointer

JOL use case

new Object code description

Try another object

Modify GC age

The GC age is stored in 4 bit s, with a maximum value of 15. When the parameter modification is greater than 15, an error will be reported and the JVM cannot be started

Automatic compression

Auto compression is on by default

Print JVM parameters using

Turn off automatic compression

Synchronized lock upgrade

Introduction of interview questions

[forced] when the concurrency is high, the performance loss of lock should be considered for synchronous call. If you can use a lock free data structure, do not use a lock; If you can lock blocks, don't lock the whole method body; If you can use object locks, don't use class locks.

Note: as far as possible, the workload of locked block is as small as possible, avoiding calling the RPC method in the lock block.

Synchronized performance changes

Why can every object become a lock

Monitor (monitor lock)

Synchronized lock optimization background

User mode and kernel mode

Lock upgrade process

The lock used for synchronized is stored in Mark Word in the Java object header. The lock upgrade function mainly depends on the lock flag bit and release bias lock flag bit in Mark Word

Lock upgrade details

No lock

Lockless case

hashcode is stored only when it is called

hashcode in hexadecimal

hashcode in binary

Conclusion: the program will not have lock competition

Bias lock

What is a bias lock:

When a piece of synchronization code has been accessed by the same thread many times, because there is only one thread, the thread will automatically obtain the lock during subsequent access

Why is there a bias lock:

After research, the author of Hotspot found that in most cases: in the case of multithreading, the lock not only does not have multithreading competition, but also has the situation that the lock is obtained by the same thread many times. Biased lock appears in this case. Its emergence is to improve the performance only when one thread performs synchronization.

Process Description:

Then you only need to record the biased thread ID when the lock is owned for the first time. In this way, the biased thread always holds the lock (when the subsequent thread enters and exits this code block with synchronization lock, it does not need to add and release the lock again. Instead, it directly compares whether the biased lock pointing to the current thread is stored in the object header).

If equality means that the biased lock is biased towards the current thread, there is no need to try to obtain the lock until the competition occurs. After each synchronization, check whether the biased thread ID of the lock is consistent with the current thread ID. if it is consistent, enter the synchronization directly. There is no need to update the object header every time the lock is unlocked. If there is only one thread that uses locks from beginning to end, it is obvious that locking bias has little additional overhead and high performance.

Upgrade to lightweight lock:

If inconsistency means competition, the lock is not always biased to the same thread. At this time, it may need to be upgraded to a lightweight lock to ensure fair competition between threads.

Biased lock the thread holding the biased lock will release the lock only when other threads try to compete for the biased lock. The thread will not actively release the biased lock.

Cancellation of bias lock

If the competing thread fails to update the object header, it will wait for the global security point (no code will be executed at this time) to revoke the bias lock.

Main role

64 bit mark graph

Holding instructions of bias lock

Example of bias lock

Lock JVM command

Demonstration of bias lock code [no effect]

Demonstration of bias lock code [ effective ]

Cancellation of bias lock

Partial lock acquisition and revocation process

Lightweight Locking

64 bit marker map

Lightweight lock acquisition

Code demonstration

Spin to a certain number of times

The difference between lightweight lock and bias lock

Heavyweight lock

Lock flag bit

Code demonstration

Lock summary

Lock elimination and lock coarsening

Lock elimination

Lock coarsening

AQS

What is it?

AbstractQueuedSynchronizer

The built-in FIFO queue is used to complete the queuing work of the resource acquisition thread, and an int class variable is used to represent the state of holding the lock

AQS related

What can I do

Locking will lead to blocking = > queuing is required if there is blocking, and queuing is necessary to realize queuing

What can I do: explain

AQS internal architecture

Official website explanation

AQS uses a volatile int type member variable to represent the synchronization State, completes the queuing of resource acquisition through the built-in FIFO queue, encapsulates each thread to preempt resources into a Node node to realize lock allocation, and modifies the State value through CAS.

ReentrantReadWriteLock

Introduction of interview questions

Lock evolution

What is a read-write lock

Lock degradation description

Read lock and write lock are mutually exclusive

Lock degradation

Lock degradation description

Java8 official website description

Can be downgraded

Non lockable upgrade

Demotion of read / write lock

The lock can be degraded and the program can execute normally

If the program cannot execute normally, it will get stuck, the write lock is not released, and the read lock cannot be obtained

Acquire read lock after holding write lock

Lock degradation of Oracle source code

StampedLock postmark lock

What is StampedLock

What is the problem of lock hunger

How to alleviate the problem of hunger

StampedLock optimistic read lock

Features of StampedLock

StampedLock three access modes

Code demonstration of three access modes

Disadvantages of StampedLock

StampedLock may cause deadlock. It is recommended not to use it at work just to cope with the interview

Conclusion

Topics: Java Back-end