JUC simple learning notes

juc concurrent programming

Introduction to juc

java.util .concurrent, Java and contract

Written examination questions

  1. Handwriting singleton mode
  2. Handwritten bubble sorting
  3. Producer consumer variants

Process / thread review

What is a process / thread?

**Process: * * process is a running activity of a program with certain independent functions about a data set. It is the basic unit of dynamic execution of the operating system. In the traditional operating system, the process is not only the basic allocation unit, but also the basic execution unit. (in short, a process is a program running in the background)

Thread: usually, a process can contain several threads. Of course, there is at least one thread in a process, otherwise it has no meaning. Threads can use the resources owned by processes. In the operating system that introduces threads, processes are usually regarded as the basic unit for allocating resources, while threads are regarded as the basic unit for independent operation and independent scheduling. Since threads are smaller than processes and basically do not own system resources, the cost of scheduling them will be much smaller, It can more efficiently improve the degree of concurrent execution among multiple programs of the system.

Process / thread example?

Using QQ, the viewing process must have A QQ Exe process, I can use QQ and A text chat, and B video chat, send files to C, send A language to D, QQ supports the search of input information.

When I was a senior, I wrote papers in word, played music with QQ music, chatted with QQ at the same time, and many processes.

If word is not saved, power off and shut down. After power on again, open word to restore the previously unsaved documents. Word will also check your spelling. There are two threads: disaster recovery backup and syntax check

What is concurrency? What is parallelism?

Concurrency: multiple threads are accessing the same resource at the same time, and multiple threads are connected to one point
Example: Xiaomi 9 was snapped up at 10 a.m. this morning
Spring Festival transportation ticket grabbing
E-commerce spike
**Parallel: * * multiple tasks are executed together and then summarized
Example: make instant noodles, boil water in an electric kettle, tear the seasoning and pour it into the bucket

thread.start

thread.start indicates that the thread enters the ready state (not called immediately). It is when the run method is called and the thread is scheduled by the cup and the operating system.

Status of Thread

Comparing wait() with sleep(), wait lets go to sleep and releases the lock in his hand.

sleep() clenched his hand and went to sleep. When he woke up, he still had the lock in his hand.

/**
 * Thread state for a thread which has not yet started.
 */
NEW,
/**
 * Thread state for a runnable thread.  A thread in the runnable
 * state is executing in the Java virtual machine but it may
 * be waiting for other resources from the operating system
 * such as processor.
 */
RUNNABLE,
/**
 * Thread state for a thread blocked waiting for a monitor lock.
 * A thread in the blocked state is waiting for a monitor lock
 * to enter a synchronized block/method or
 * reenter a synchronized block/method after calling
 * {@link Object#wait() Object.wait}.
 */
BLOCKED,

WAITING and timed_ The difference between WAITING

WAITING is WAITING all the time. It's commonly known as WAITING until you see each other

The thread state of the waiting thread.

Due to a call

The following methods:

/**
 * Thread state for a waiting thread.
 * A thread is in the waiting state due to calling one of the
 * following methods:
 * <ul>
 *   <li>{@link Object#wait() Object.wait} with no timeout</li>
 *   <li>{@link #join() Thread.join} with no timeout</li>
 *   <li>{@link LockSupport#park() LockSupport.park}</li>
 * </ul>
 *
 * <p>A thread in the waiting state is waiting for another thread to
 * perform a particular action.
 *
 * For example, a thread that has called <tt>Object.wait()</tt>
 * on an object is waiting for another thread to call
 * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
 * that object. A thread that has called <tt>Thread.join()</tt>
 * is waiting for a specified thread to terminate.
 */
WAITING,

TIMED_WAITING is waiting regularly, commonly known as waiting out of date

The thread state of the waiting thread with the specified waiting time.

Due to calling one of the threads, the thread is in a timed waiting state

Have the following methods to specify the waiting time:

/**
 * Thread state for a waiting thread with a specified waiting time.
 * A thread is in the timed waiting state due to calling one of
 * the following methods with a specified positive waiting time:
 * <ul>
 *   <li>{@link #sleep Thread.sleep}</li>
 *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
 *   <li>{@link #join(long) Thread.join} with timeout</li>
 *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
 *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
 * </ul>
 */
TIMED_WAITING,

Classic ticket selling problem

The general multithreading problem can be simplified as: thread operation (method exposed by resource class) resource class

Version 1 uses anonymous inner classes

Resource class: you can also use synchronization methods or synchronization code blocks here (but the granularity is too large)

The implementation class of Lock used here is ReentrantLock(), which is more flexible than synchronized synchronization Lock,

/**
 * Resource category: ticket
 */
class Ticket1 {

    // Set the initial value to 30 sheets
    private int number = 30;

    // ReentrantLock reentrant lock
    private Lock lock = new ReentrantLock();

    /**
     * Operation. Using synchronized here is equivalent to locking all the saleTicket. The granularity is too large
     */
    public void saleTicket() {


        // Lock
        lock.lock();
        try {
            // First judge whether number is greater than 0
            if (number > 0) {
                System.out.println("ThreadName = " + Thread.currentThread().getName() + "\t Sold article:" + (number--) + "\t Remaining" + number);
            } else {
                System.out.println("The tickets have been sold out");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Release lock
            lock.unlock();
        }


    }


}

Ticket grabbing

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/03    10:10
 * @Version:1.0
 *
 * Title: three conductors sell 30 tickets
 *Enterprise routine + template of multithreaded programming
 *
 * 1.On the premise of high cohesion and low coupling: thread operation (calling method exposed by resource class) resource class
 *
 *Ticket grabbing
 *
 */
public class SaleTicket {

    public static void main(String[] args) {  // main program all entries




        // Create two threads t1, t2
//        Thread t1 = new Thread();
//        Thread t2 = new Thread();

        //Thread(Runnable target, String name)
//        //Start thread
//        t1.start();
//        t2.start();


        // The following three threads (A, B, C) operate on the same resource class
        Ticket1 ticket = new Ticket1();
        // Anonymous Inner Class 

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i <= 40; i++) {
                    ticket.saleTicket();
                }
            }
        }, "A");


        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i <= 40; i++) {
                    ticket.saleTicket();
                }
            }
        }, "B").start();


        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i <= 40; i++) {
                    ticket.saleTicket();
                }
            }
        }, "C").start();


    }


}

Version 2 uses lambda expressions

Scenario used by Lambda Express: when there is only one abstract method in an interface, it doesn't matter how many other default methods there are.

Small formula of Lambda Express expression: copy parentheses (parentheses are methods in the interface), write dead right arrow, and landing braces.

@The functional interface annotation is a declarative functional interface. In fact, it can be added by default at the bottom

Foo interface

/**
 * An interface with only one abstract method is called a "functional interface"
 */
@FunctionalInterface
interface Foo {
//    public void sayHello();

    int add(int a, int b);


    default int div(int a, int b) {
        return a / b;
    }

    /**
     * Static method implementation
     * @param a
     * @param b
     * @return
     */
    static int mv(int a, int b) {

        return a * b;
    }


}

Use Lambda expressions in the Foo interface

public class LambdaExpressDemo {

    public static void main(String[] args) {


    /*        Foo foo = new Foo() {
            @Override
            public void sayHello() {
                System.out.println("*******************hello java 2022");
            }
        };*/

/*
        Foo foo = () -> {
            System.out.println("*******************hello java lambda");

        };


        foo.sayHello();
*/

        // The parameter type of the method can be omitted
        Foo foo = (a, b) -> {

            System.out.println("a+b = " + a + b);

            return a + b;
        };

        foo.add(1234, 45);

        System.out.println(foo.div(10, 2));

        System.out.println(Foo.mv(5, 3));


    }


}

Thread interaction problem: alternate printing 0 and 1 of written test questions

This is the producer and consumer model.

Written examination questions:
Title: now two threads can operate a variable with an initial value of zero,
Realize that one thread adds to the variable and one thread subtracts i from the variable,
Realize alternation, for 10 rounds, and the initial value of the variable is zero.

Two threads print version 1.0 alternately

There is a pit here, but it will not appear when there are two threads. It will appear when there are many threads. The judgment condition here cannot use if, but can only use while.

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/03    18:29
 * @Version:1.0
 * Written examination questions:
 * Title: now two threads can operate a variable with an initial value of zero,
 * Realize that one thread adds to the variable and one thread subtracts i from the variable,
 * Realize alternation, for 10 rounds, and the initial value of the variable is zero.
 *
 *
 * // 1.On the premise of high cohesion and low coupling, threads operate resource classes
 *    2.   Judgment / work / notice
 *
 * Producer consumer review
 *
 *
 *
 */
public class ThreadWaitNotifyDemo {

    public static void main(String[] args) {

        AirCondition airCondition = new AirCondition();


        /**
         * Make cakes and add
         */
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airCondition.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }, "A").start();


        /**
         * Consumption cake, subtraction
         */
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airCondition.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();


    }
}

/**
 * Resource class
 *  1.0 edition
 */
class AirCondition {
    private int number = 0;


    /**
     * Add 1 to number
     * There are three steps,
     * 1,Judge whether the value of number is 0
     * 2,add 
     * 3,Inform consumers to eat cake
     *
     * Producer thread
     *
     */
    public synchronized void increment() throws InterruptedException {


        // 1. Judgment
        if (number != 0) {
            this.wait();
        }

        // 2. Work
        number++;

        System.out.println(Thread.currentThread().getName() + "\t" + number);

        // 3. Notification (wake up the waiting thread)
        this.notifyAll();


    }


    /**
     * Subtract 1 from number
     * synchronized Add synchronization lock
     * It is equivalent to that consumers buy cakes. If there is no cake, they have to wait first
     * Consumer thread
     *
     *
     */
    public synchronized void decrement() throws InterruptedException {


        // 1. Judge, equal to 0. If there is no cake, you have to wait first
        if (number == 0) {
            this.wait();
        }

        // 2. Work
        number--;

        System.out.println(Thread.currentThread().getName() + "\t" + number);

        // 3. Notification (wake up the waiting thread)
        this.notifyAll();


    }

}

Two threads print version 2.0 alternately (become four threads)

Here, it is upgraded to four threads for operation, one plus one minus, one plus one minus

/* if (number != 0) {

        //  When A does not execute the following wait, the thread scheduling stops,
        //  But after he woke up, he didn't make a judgment, because number may have been added to 1 (the same is true for the following consumers)
        // Keep waiting to enter the blocking state. When there are other threads, notify() or notifyAll() will be awakened
        this.wait();
    }*/
public class ThreadWaitNotifyDemo2 {

    public static void main(String[] args) {
        AirCondition1 ac1 = new AirCondition1();

        // Consumer thread A
        new Thread(() -> {

            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(200);
                    ac1.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();


        // Consumer thread B
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(400);
                    ac1.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();


        // Consumer thread C
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(500);
                    ac1.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();


        // Consumer thread D
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    ac1.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();


    }


}


/**
 * Resource class
 * 2.0 edition
 */
class AirCondition1 {
    private int number = 0;


    /**
     * Add 1 to number
     * There are three steps,
     * 1,Judge whether the value of number is 0
     * 2,add 
     * 3,Inform consumers to eat cake
     *
     * Producer thread
     *
     */
    public synchronized void increment() throws InterruptedException {


        /**
         * sleep The ownership (lock) of the thread will not be released, but the wait will, so the following is used in conjunction with synchronized
         *
         * This method calls the parameterless wait
         * Causes the current thread to wait until another thread calls the notify() method or notifyAll() method of the object.
         * In other words, the behavior of this method is like simply calling wait(0).
         *
         *
         * wait()
         * The current thread must own the display of the object. The thread releases ownership (lock) of this monitor and waits until one of the following two conditions occurs:
         *
         * Another thread notifies the thread of the monitor waiting for the object to wake up by calling the notify method or notifyAll method.
         * The timeout specified by the timeout milliseconds plus nanos econds parameter has expired.
         *
         *
         *
         * Like in a parametric version, interrupts and false wakes are possible, and the method should always be used in the loop:
         *
         *   synchronized (obj) {
         *          while (<condition does not hold>)
         *              obj.wait(timeout, nanos);
         *          ... // Perform action appropriate to condition
         *      }
         *
         *
         */
        // 1. Judgment


        // Note that if cannot be used to judge here. If it is used, false wake-up will occur, because if only judges once (this is the main reason)
/*        if (number != 0) {

            //  A The thread may stop scheduling when the following wait is not executed,
            //  But after he woke up, he didn't make a judgment, because number may have been added to 1 (the same is true for the following consumers)
            // Keep waiting to enter the blocking state. When there are other threads, notify() or notifyAll() will be awakened
            this.wait();
        }*/


        // 1. Judgment
        while (number != 0) {
            // Keep waiting to enter the blocking state. When there are other threads, notify() or notifyAll() will be awakened
            this.wait();
        }


        // 2. Work
        number++;

        System.out.println(Thread.currentThread().getName() + "Production data:" + number);

        // 3. Notification (wake up the waiting thread)
        this.notifyAll();


    }


    /**
     * Subtract 1 from number
     * synchronized Add synchronization lock
     * It is equivalent to that consumers buy cakes. If there is no cake, they have to wait first
     * Consumer thread
     *
     *
     */
    public synchronized void decrement() throws InterruptedException {


        // 1. Judge, equal to 0. If there is no cake, you have to wait first
        while (number == 0) {
            this.wait();
        }


        // 2. Work
        number--;

        System.out.println(Thread.currentThread().getName() + "Consumption data:" + number);

        // 3. Notification (wake up the waiting thread)
        this.notifyAll();


    }

}

Adaptation topic

Topic content

Multiple threads are called in order to realize A->B->C Three threads are started. The requirements are as follows:
AA Print 5 times, BB Ten times, CC Print 15 times, then  (Here is the communication between threads,Here is the exact notice)
AA Print 5 times, BB Print 10 times, CC Print 15 times

Since there are three threads for printing and there is order, use the flag bit plus signal() in the Condition interface; Achieve accurate notification. When thread A enters and executes the printing operation, it changes the flag bit and accurately notifies thread B. Similarly, B notifies C after completion, and C then notifies A to realize A - > b - > C (in fact, the number flag bit plays A role)

The code of the resource class is as follows

  • The first one uses Lock lock
/**
 * Resource class
 * Job: merge three methods into one method
 */
class ShareResource {
    // 1: A 2: B 3: C flag bit 1 for A,2 corresponds to B and 3 corresponds to C
    private int number = 1;    // Set the initial value of the flag bit to 1
    // Lock is equivalent to a lock, and the following condition is equivalent to a key. One lock is equipped with three keys
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    /**
     * Print five times
     */
    public void print5() {

        lock.lock();
        try {

            // 1. Judgment
            while (number != 1) {
                // Wait
                condition1.await();
            }

            // 2. Work
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + ":\t" + i);
            }

            // 3. Notice
            // Change the flag bit
            number = 2;

            // Here, wake up 2 threads accurately
            // Wake up condition2 locked thread (wake up 2 thread)
            condition2.signal();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Print ten times
     */
    public void print10() {

        lock.lock();

        try {

            // 1. Judgment
            while (number != 2) {
                // Wait
                condition2.await();
            }

            // 2. Work
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + ":\t" + i);
            }

            // 3. Notice
            // Change the flag bit
            number = 3;
            // Wake up waiting thread (wake up 3 thread)
            condition3.signal();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Print fifteen times
     */
    public void print15() {

        lock.lock();

        try {

            // 1. Judgment
            while (number != 3) {
                // Wait
                condition3.await();
            }

            // 2. Work
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + ":\t" + i);
            }

            // 3. Notice
            // Change the flag bit
            number = 1;
            // Wake up waiting thread (wake up 1 thread)
            condition1.signal();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  • The second method uses synchronization
/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/07    9:15
 * @Version:1.0
 * You can also use the synchronized synchronization method and wait and notifyAll. In fact, the number flag bit works
 *
 */
class AirCondition3{

    // 1 for A, 2 for B, and 3 for C
    private int number = 1;


    public synchronized void print5s() {

        // judge
        while (number != 1) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + "\t\t" + i);
        }
        number = 2;
        notifyAll();
    }

    public synchronized void print10s() {

        // judge
        while (number != 2) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName() + "\t\t" + i);
        }
        number = 3;
        notifyAll();
    }

    public synchronized void print15s() {

        // judge
        while (number != 3) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (int i = 1; i <= 15; i++) {
            System.out.println(Thread.currentThread().getName() + "\t\t" + i);
        }
        number = 1;
        notifyAll();
    }


}
public class ThreadWaitNotifyDemo1 {

    public static void main(String[] args) {
        AirCondition3 airCondition2 = new AirCondition3();
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                airCondition2.print5s();
            }
        }, "thread  A").start();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                airCondition2.print10s();
            }
        }, "thread  B").start();
        new Thread(() -> {
            for (int i = 1; i <= 15; i++) {
                airCondition2.print15s();
            }
        }, "thread  C").start();
    }


}


Replace synchronized with lock

Replace the synchronized synchronization method with a Lock lock, which is more flexible. * * synchronized uses wait and notify/notifyAll in the Object class** In Lock, the Lock implementation provides a wider range of locking operations than can be obtained using synchronized methods and statements. They allow more flexible structuring, may have completely different properties, and can support multiple associated Object conditions.

Condition factor out Object monitor method( waitnotify and notifyAll )Into different objects to get each Object with multiple waiting sets, which is realized by using any combination of them and lock. Lock replaces the use of synchronized methods and statements, and condition replaces the use of Object monitor methods. Condition uses await() and signal() /signalAll() corresponding to wait and notify/notifyAll of the Object class.

Code of resource class and operation class.

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/04    16:49
 * @Version:1.0
 *
 * Replace the Lock with Lock
 * 1.On the premise of high cohesion and low coupling, threads operate resource classes
 * 2.   Judgment / work / notice
 * 3.   In the process of multi-threaded interaction, it is necessary to prevent false wake-up of multi threads, that is (judge to use while instead of if)
 * (It is not allowed to use if in the judgment of method, but only use while)
 * 4.   Pay attention to the modification and positioning of the flag bit
 *
 */
public class ThreadWaitNotifyDemo2 {
    public static void main(String[] args) {
        AirCondition2 airCondition2 = new AirCondition2();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    airCondition2.increase();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread  A").start();

            new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    airCondition2.decrease();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread  B").start();
            new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    airCondition2.increase();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread  C").start();
            new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    airCondition2.decrease();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread  D").start();
    }
}

class AirCondition2 {

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    private int number = 0;


    // The producer adds 1 to the value of number
    public void increase() throws InterruptedException {

        // Lock resources
        lock.lock();

        try {
            // 1. Judgment
            while (number != 0) {
            //    this.wait();
                condition.await();
            }

            // 2. Work
            ++number;
            System.out.println(Thread.currentThread().getName() + "Produced" +"\t"+ number);
            // notice
//            this.notify();
            condition.signalAll();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Release locks on resources
            lock.unlock();
        }


    }


    // The producer subtracts 1 from the value of number
    public void decrease() throws InterruptedException {

        // Lock resources
        lock.lock();

        try {
            // 1. Judgment
            while (number == 0) {
                //    this.wait();
                condition.await();
            }

            // 2. Work
            number--;
            System.out.println(Thread.currentThread().getName() + "Consumption" +"\t"+ number);
            // notice
//            this.notify();
            condition.signalAll();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Release locks on resources
            lock.unlock();
        }

}
    }

Multithreaded 8 lock

Title: multithreading 8 lock
    In fact, the following is to see synchronized What is the object of the lock
    The common synchronization method locks this The current object, rather than locking the individual synchronized Method, that is, at the same time, only one thread can enter the current class and access one synchronized method
    Static synchronization methods lock classes.Class This object is also the class object. At the same time, review it static keyword
    Common methods do not add synchronized It won't lock
    For synchronous method blocks, the lock is Synchonized Objects configured in parentheses
A If there are multiple objects in an object synchronized Method. At a certain time, only one thread calls one of them synchronized There's no way,
Other threads can only wait. In other words, only one thread can access these at a certain time synchronized method
 The lock is the current object this,After being locked, other threads cannot enter other threads of the current object synchronized method
 
After adding a common method, it is found that it has nothing to do with the synchronization lock
 After changing to two objects, the lock is not the same, and the situation changes immediately.
 
 
synchronized The basis of synchronization: Java Each object in can be used as a lock.
It is embodied in the following three forms.
For normal synchronization methods, the lock is the current instance object.
For static synchronization methods, the lock is the of the current class Class Object.
For synchronous method blocks, the lock is Synchonized Objects configured in parentheses
 
When a thread attempts to access a synchronized code block, it must first get the lock and release the lock when exiting or throwing an exception.
 
That is, if the non static synchronization method of an instance object acquires a lock, the other non static synchronization methods of the instance object must wait for the method acquiring the lock to release the lock before acquiring the lock,
However, the non static synchronization methods of other instance objects use different locks from the non static synchronization methods of this instance object,
Therefore, the instance object can acquire its own lock without waiting for the non static synchronous method that has acquired the lock to release the lock.
 
All static synchronization methods use the same lock - the class object itself,
These two locks are two different objects, so there will be no race condition between static synchronization methods and non static synchronization methods.
However, once a static synchronization method acquires a lock, other static synchronization methods must wait for the method to release the lock before acquiring the lock,
Whether it is between static synchronization methods of the same instance object,
Or between static synchronization methods of different instance objects, as long as they are instance objects of the same class!
 
 
 

  • 1. Standard access, print SMS or email first

mail

  • 2. Stop for 4 seconds. In the SMS method, print the SMS or email first

mail

  • 3. The common Hello method is to send a text message or hello first

hello

  • 4. Now there are two mobile phones. First print SMS or email

short message

  • 5. Two static synchronization methods, one mobile phone, print SMS or email first

mail

  • 6. Two static synchronization methods, two mobile phones, print SMS or email first

mail

  • 7. 1 static synchronization method, 1 ordinary synchronization method, 1 mobile phone, print SMS or email first

short message

  • 8. 1 static synchronization method, 1 ordinary synchronization method, 2 mobile phones, print SMS or email first

short message

public class Lock8 {
    public static void main(String[] args) throws InterruptedException {


        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone.sendEmail();
        }, "thread  A").start();

        // Since thread A sleeps for 0.2 seconds, thread A has completed startup and scheduling
        Thread.sleep(200);

        new Thread(() -> {
//                phone.sendMsg();


            phone2.sendMsg();
//            phone.hello();
        }, "thread  B").start();


    }


}

class Phone {


    /**
     * Method of sending mail
     */
    public static synchronized void sendEmail() {
        try {
            // The thread sleeps for four seconds
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "---------------sendEmail");
    }


    /**
     * Sending SMS method
     */
    public  static synchronized void sendMsg() {
        System.out.println(Thread.currentThread().getName() + "---------------sendMsg");
    }


    /**
     * Common method: if you don't lock, you don't have to compete for lock resources
     */
    public void hello() {
        System.out.println(Thread.currentThread().getName() + "-------------------------hello");
    }

}

Print odd and even numbers within 100 alternately

There is a big pit here. You have to wake up another lock first, and then put yourself into the waiting state. Otherwise, there will be a deadlock, and even the brother thread is waiting for the other party to wake up.

deadlock

**It refers to a deadlock caused by multiple processes competing for resources during operation. When the processes are in this deadlock state, they will not be able to move forward without the action of external forces. Therefore, let's take an example to describe that if there is a thread T1 at this time, it obtains the lock in the order of locking R1 and then obtaining the lock R2, and at the same time, another thread T2 obtains the lock in the order of locking T2 and then locking T1.

The first one uses Lock lock

public class Test2 {
    public static void main(String[] args) {

        ShareSource1 ss = new ShareSource1();
        new Thread(()->{ss.print();},"thread  A").start();
        new Thread(()->{ss.print();},"thread  B").start();


    }

}

class ShareSource1 {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private int number = 1;

    public void print() {

        // judge
        lock.lock();
        try {
            while (number <= 100) {
                System.out.println(Thread.currentThread().getName() + "\t\t" + number);
                number++;
                //    Thread deadlock describes a situation in which multiple threads are blocked at the same time, and one or all of them are waiting for a resource to be released. Because the thread is blocked indefinitely, the program cannot terminate normally
                // You can't wait first, because if you wait first and then B comes in, it's also a wait, and then they wait for each other, which will deadlock
//                this.wait();
//                this.notify();
                // In order to avoid deadlock, you have to wake up first and then wait

                condition.signal();
                condition.await();

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

The second uses synchronized static blocks of code

public class Test3 {
    public static void main(String[] args) {
        ShareSource2 ss2 = new ShareSource2();
        new Thread(() -> {
            try {
                ss2.print();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread  A").start();
        new Thread(() -> {
            try {
                ss2.print();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread  B").start();
    }
}

class ShareSource2 {


    private int number = 1;

    public void print() throws InterruptedException {

        // judge

        synchronized (this) {
       while (number <= 100) {
                System.out.println(Thread.currentThread().getName() + "\t\t" + number);
                number++;


                //    Thread deadlock describes a situation in which multiple threads are blocked at the same time, and one or all of them are waiting for a resource to be released. Because the thread is blocked indefinitely, the program cannot terminate normally
                // You can't wait first, because if you wait first and then B comes in, it's also a wait, and then they wait for each other, which will deadlock
//                this.wait();
//                this.notify();
                // In order to avoid deadlock, you have to wake up first and then wait
                this.notify();
                this.wait();
            }
        }
    }
}

Some comparisons in the set

Please give an example to illustrate that the collection class is unsafe

ArrayList thread is unsafe

ArrayList, HashMap and HashSet are thread unsafe

List<String> list = new ArrayList<>();   // Collections.synchronizedList(new ArrayList<String>());


// Exception Java util. ConcurrentModificationException
// Under the condition of multithreading, it is necessary to read and write
for (int i = 1; i <= 30; i++) {

    new Thread(() -> {
        list.add(UUID.randomUUID().toString().substring(0, 8));
        System.out.println(Thread.currentThread().getName() + list);
    }, String.valueOf(i)).start();

}
  1. Fault phenomenon

    java. util. Concurrent modificationexception concurrent modification exception

  2. Cause
    Under the condition of multithreading, it is necessary to read and write

  3. Solution

    3.1 using Vector, its bottom layer adds synchronization lock in the add method
    3.2 Collections.synchronizedList(new ArrayList());
    3.3 new CopyOnWriteArrayList<>();

  4. Shortcut for extracting code blocks into methods Ctrl+Alt+m

add method of ArrayList

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

Do not use ArrayList in multithreaded conditions

   public static void main(String[] args) {

/*        // Convert array to List
        List<String> list = Arrays.asList("a", "b", "c");
        // Method reference
        list.forEach(System.out ::println);*/

        // Generate current timestamp
        //    System.currentTimeMillis();

        // The first method: (thread unsafe) new ArrayList < > ();
        // The second method (not recommended) new vector < > ();
        // Third: use collections Synchronizedlist converts a thread unsafe List to a thread safe List
        // The fourth method: use new copyonwritearraylist < > ();


        // HashMap thread is unsafe. Use ConcurrentHashMap

        List<String> list = new ArrayList<>();   // Collections.synchronizedList(new ArrayList<String>());


        // Exception Java util. ConcurrentModificationException
        // Under the condition of multithreading, it is necessary to read and write
        for (int i = 1; i <= 30; i++) {

            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(Thread.currentThread().getName() + list);
            }, String.valueOf(i)).start();

        }



    }

java.util.concurrent

Under the condition of multithreading, the following classes are generally used, and the latter three are Java util. Concurrent and contract the following.

  • ArrayList ------> CopyOnWriteArrayList
  • HashSet ------> CopyOnWriteArraySet
  • HashMap ------> ConcurrentHashMap

Basic knowledge of recall set

The underlying data structure of HashSet is HashMap, which stores the key of map, and its value is a constant of a dead Object

The add method of HashSet is the put method of the called HashMap

The bottom 1.8 of HashMap is Node array + linked list + red black tree, and 1.7 is hash table

jdk1.8

Construct an empty HashMap. The default initial capacity is (16) and the default load factor is (0.75)

HashMap and HashSet are disordered

Disorder: disorder is not equal to randomness. It refers to that the stored data is not added in the order of array index in the underlying array, but determined according to the hash value of the value.

The capacity of ArrayList is expanded by 1.5 times

HashMap is expanded to twice its original capacity. When in use, its initial capacity can be set to be large, so that it can avoid repeated capacity expansion.

CopyOnWriteArrayList

  • java.util.concurrent.CopyOnWriteArrayList

Principle of copy on write technology

**The CopyOnWrite container is the container that is copied when writing** When adding elements to a container, you do not directly add them to the current container Object [],
Instead, first Copy the current container Object [] to create a new container Object[] newElements, and then add elements to the new container Object[] newElements.
After adding the element, point the reference of the original container to the new container setarray (new elements).
The advantage of this is that the CopyOnWrite container can be read concurrently without locking, because the current container will not add any elements.
Therefore, the CopyOnWrite container is also an idea of separating reading and writing. Reading and writing are different containers.

Principle of CopyOnWriteArrayList: in short, when A thread writes, first copy A version 1.1, and then write it by itself. If other threads want to read, read version 1.0, and release version 1.1 after A is finished. (in fact, the idea of separation of reading and writing)

CopyOnWriteArrayList is thread safe. The following is the source code of its add method. It will first copy the incoming collection, and then copy the new one

   /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }
   

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // Here is to copy a new one and receive it with the Object array
        Object[] elements = getArray();
        // Get the length of the original set
        int len = elements.length;
        // Expand the elements array and add one to the length to get a new newElements
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // Add the e to be added to the end of the array
        newElements[len] = e;
        // Resetting the newElements back to the CopyOnWriteArrayList object is equivalent to replacing version 1.0 with version 1.1 
        setArray(newElements);
        // Add complete
        return true;
    } finally {
        // Release lock
        lock.unlock();
    }
}

The put method of HashMap actually contains Node nodes

    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }


    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // The put of HashMap is loaded with Node nodes
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

Callable interface

Review ways to get multithreaded

Interview question: how many ways to get multithreading?

  • Inherit Thread class
/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/06    19:39
 * @Version:1.0
 * The second way to realize multithreading is to inherit the Thread class and rewrite the run method
 *
 *
 */
public class ThreadDemo1 {

    public static void main(String[] args) {

        MyThread1 t1 = new MyThread1();

        new Thread(t1,"Thread 1").start();
        new Thread(t1,"Thread 2").start();

/*
        t1.setName("Thread A ");
        t1.start();
        MyThread1 t2 = new MyThread1();
        t2.setName("Thread B ");
        t2.start();*/

    }


}

class MyThread1 extends Thread {
    private static int ticket = 100;


    @Override
    public void run() {
        synchronized (this) {
            while (true) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "\t\t" + ticket);
                    ticket--;
                } else {
                    break;
                }

            }

        }

    }
}
  • Implement Runnable interface
/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/06    19:35
 * @Version:1.0
 * Method 1 of realizing multithreading
 * Implement Runnable interface
 */
class MyThread implements Runnable {
    private int number = 100;

    @Override
    public  void run() {

        synchronized (this) {
            while (true) {
                if (number>0){
                    System.out.println(Thread.currentThread().getName() +"\t\t"+ number);
                    number--;
                }else{
                    break;
                }

            }
        }


    }
}




public class ThreadDemo {
    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        new Thread(myThread,"thread  A").start();
        new Thread(myThread,"thread  B").start();

    }


}

Simple use of Callable

  • Implement Callable interface

The third way to implement multithreading is to implement the Callable interface. The first two are too simple to write

Generally, the get method should be placed on the last line, because if there is a block in the implemented Callable class, it will not execute the following until it is completed

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/06    20:11
 * @Version:1.0
 *
 * Select the interface, and then use Ctrl+Alt+U to view the class diagram of this class or interface
 * get Methods are usually placed on the last line, because if there is a block in the Callable class of the implementation
 * It will not execute the following until it is finished
 *In·
 */

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

/**
 * Multithreading implementation method 3 implements Callable interface
 */
class MyThread2 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"\t\t  hello world");
        TimeUnit.SECONDS.sleep(4);
        return 1024;
    }
}

public class CallableDemo {

    public static void main(String[] args) throws Exception {

        // Interface RunnableFuture < V > is a sub interface of Runnable. FutureTask implements the RunnableFuture interface
        // A construction method of FutureTask futuretask (callable < V > callable)
        // Thread(Runnable target, String name). In the constructor of thread, just pass in Runnable or Runnable sub interface or implementation class
        // Here is mainly the idea of polymorphism
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread2());
        /**
         * Print results
         * hello world
         * 1024
         * main Calculation completed
         * hello world will only be printed once because it has a cache
         */
        new Thread(futureTask,"thread  a").start();
        new Thread(futureTask,"thread  b").start();
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName() + "Calculation completed");
        
    }

}

Interview question: what is the difference between callable interface and runnable interface?

Answer: (1) is there a return value
(2) Whether to throw abnormality
(3) The landing methods are different. One is run and the other is call

FutureTask

For future tasks, use it to do one thing, asynchronous call
The main method is like an ice sugar gourd. Each method is connected by main.
But it can't solve one problem: the normal call is suspended and blocked

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-byt2g7y5-1643028768964) (C: \ users \ losser \ appdata \ roaming \ typora user images \ image-20220108213632391. PNG)]

example:

(1) The teacher is thirsty in class. It's not appropriate to buy water. The lecture thread continues. I can start a single thread and ask the monitor to help buy water,
The water is bought and put on the table. I'll get it when I need it.
(2) Four students, A is 1+20,B is 21+30,C is 31 * to 40, and D is 41 + 50. Is it A little big for C,
FutureTask starts a single thread for C calculation. I summarize ABD first, and then summarize C after C calculation to get the final result
(3) College entrance examination: do what you can do first, and do what you can't do later

principle

When you need to perform more time-consuming operations in the main thread, but you don't want to block the main thread, you can hand over these jobs to the Future object to complete in the background,
When the main thread needs it in the Future, it can obtain the calculation results or execution status of the background job through the Future object.

Generally, FutureTask is mostly used for time-consuming calculation. The main thread can obtain the results after completing its own task.

The results can be retrieved only when the calculation is completed; Block the get method if the calculation is not yet complete. Once the calculation is completed,
You can't restart or cancel the calculation. get method, and the obtained result can only be obtained when the calculation is completed, otherwise it will be blocked until the task turns to the completed state,
Then it will return the result or throw an exception.

Calculate only once
get method last

FutureTask class diagram [the external chain picture transfer fails, and the source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-4tkbdrhl-1643028768964) (C: \ users \ losser \ appdata \ roaming \ typora \ typora user images \ image-20220107131432435. PNG)]

Explanation of JUC's powerful auxiliary classes

CountDownLatch decrease count

principle

  • CountDownLatch mainly has two methods. When one or more threads call the await method, these threads will block.
  • Calling the countDown method by other threads will reduce the counter by 1 (the thread calling the countDown method will not block),
  • When the value of the counter changes to 0, the thread blocked by await method will wake up and continue to execute.

code

An example in life is the self-study at night and the monitor closes the door, that is, the monitor closes the door after all the students go out to reduce the count

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/07    15:19
 * @Version:1.0
 * CountDownLatch
 * This is similar to the example in life: in the evening self-study, the monitor closes the door, that is, the monitor closes the door after all the students go out
 * Reduce count
 */
public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        // Construct a countdownlatch with a given count. java.util.concurrent
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "Leave the classroom");
                //countDown() reduces the count of latches and releases all waiting threads if the count reaches zero.
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        // Causes the current thread to wait until the latch count reaches zero, unless the thread is interrupted,
        // In fact, when the count inside is reduced to 0, there is no need to wait and wake up the thread
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t The monitor closed the classroom");
    }
    private static void closeDoor() {
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "Leave the classroom");
            }, String.valueOf(i)).start();

        }
    System.out.println(Thread.currentThread().getName() + "\t The monitor closed the classroom");
    }
}

CyclicBarrier circular fence

principle

Cyclic Barrier literally means a Barrier that can be used cyclically. What it needs to do is to block a group of threads when they reach a Barrier (also known as synchronization point). The Barrier will not open until the last thread reaches the Barrier, and all threads intercepted by the Barrier will continue to work. The thread enters the Barrier and passes through the await() method of CyclicBarrier.

A simple example of this is to gather seven dragon balls to summon the dragon, and to hold a meeting. You can't hold a meeting until everyone arrives.

code

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/07    15:55
 * @Version:1.0
 * java.util.concurrent  CyclicBarrier
 * In fact, this is a bit like an example in life: there are seven dragon balls in a meeting. Gather seven dragon balls to summon the divine dragon
 * The meeting must wait for everyone to arrive
 *
 *
 */
public class CyclicBarrierDemo {

    public static void main(String[] args) {

        CyclicBarrier cb = new CyclicBarrier(7, () -> {
            System.out.println("Summon the Dragon");
        });

        /**
         * Wait until all parties have called await on this obstacle.
         * If the current thread is not the last thread, it is disabled for thread scheduling and is dormant until one of the following occurs:
         * The last thread arrives; or
         * Some other threads are interrupts; or
         * Some other threads interrupt one of the other waiting threads; or
         * Some other threads timed out while waiting for the barrier; or
         * Some other threads call reset() on this barrier.
         */

        for (int i = 1; i <= 7; i++) {
            int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "Collected\t The first" + temp + "Dragon Ball");
                try {
                    //Wait until all parties have called await on this obstacle.
                    //If the current thread is not the last thread, it is disabled for thread scheduling and is dormant until one of the following occurs: the last thread arrives
                    // That is, wait until the last thread arrives, and then start. After start, the scheduling order of threads depends on the scheduling of the operating system and cup
                    cb.await();
                    // After execution, call the barrierAction thread in CyclicBarrier(int parties, Runnable barrierAction)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, "thread \t" + i).start();

        }


    }


}

Semaphore signal light

principle

In terms of semaphores, we define two operations:

Acquire when a thread invokes the acquire operation, it either acquires the semaphore successfully (semaphore minus 1), or waits until a thread releases the semaphore, or times out.

release actually increases the value of the semaphore by 1 and wakes up the waiting thread.

Semaphores are mainly used for two purposes: one is for mutually exclusive use of multiple shared resources, and the other is for controlling the number of concurrent threads.

This can be compared with grabbing parking spaces in life

code

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/07    16:40
 * @Version:1.0
 * Semaphore This is mainly used to grab parking spaces
 * This is mainly used for multithreading concurrency control and mutual exclusion of resources
 *
 *
 */
public class SemaphoreDemo {


    public static void main(String[] args) {




        // Simulated resource class, with three empty parking spaces
        // When permissions is set to 1, it is equivalent to synchronized
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 7; i++) {


            new Thread(() -> {
                try {
                    //Obtain a license from this semaphore and block until available, otherwise the thread is interrupted.
                    //Obtain a license, if available and return immediately, reduce the number of available licenses by one.
                    // This is equivalent to seizing the parking space
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t Grabbed the parking space");
                    // The thread slept for 4 seconds and simulated parking for 4 seconds
                    TimeUnit.SECONDS.sleep(4);
                    System.out.println(Thread.currentThread().getName() + "\t Left the parking space");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //Issue licenses and increase the number of available licenses by one. If any thread attempts to obtain a license,
                    // Then choose a license that has just been released. (RE) thread scheduling is used for thread scheduling.
                    // This is equivalent to releasing the parking space
                    semaphore.release();
                }
            }, "thread " + i).start();
        }
    }
}

ReentrantReadWriteLock

Similar cases

Update and query operations of Starscream, cache and database.

code

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/07    22:00
 * @Version:1.0
 * There is no problem for multiple threads to read a resource class at the same time, so in order to meet the concurrency, reading shared resources should be carried out at the same time
 * however
 * If a thread wants to write to a shared resource, no other thread should be able to read or write to the resource
 * Summary:
 *          Read read coexistence
 *          Read write cannot coexist
 *          Write write cannot coexist
 * The read-write lock is used to ensure the consistency of data. The operation read lock is a shared lock
 * In fact, write lock is added to write lock, read lock is added to read lock, and write lock is exclusive lock
 *
 *
 */
class MyCache {

    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * Add operation
     * @param key
     * @param value
     */
    public void put(String key, Object value) {

        try {
            // Write lock
            readWriteLock.writeLock().lock();
            // Write start
            System.out.println(Thread.currentThread().getName() + "\t------Write start");
            map.put(key, value);
            TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName() + "\t------Write complete");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Release lock
            readWriteLock.writeLock().unlock();
        }
    }

    /**
     * read operation
     * @param key
     */
    public void get(String key) {

        try {
            // Write lock
            readWriteLock.readLock().lock();
            // Read start
            System.out.println(Thread.currentThread().getName() + "\t~~~~~~~~~~~~~~~~~~~~~~Read start");
            map.get(key);
            TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName() + "\t~~~~~~~~~~~~~~~~~~~~~~" +
                    ".Read complete");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Release lock
            readWriteLock.readLock().unlock();
        }
    }

}

public class ReadWriteLockDemo {
    public static void main(String[] args) {

        MyCache myCache = new MyCache();
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {

                myCache.put(temp + "\t", temp + "\t");
            }, String.valueOf(i)).start();

        }
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "\t");
            }, String.valueOf(i)).start();
        }

    }
}

Blocked queues cannot be consumed when empty and cannot be increased when full

BlockingQueueDemo blocking queue

Stack and queue

Stack: first in first out, last in first out

Queue: first in first out

Brief description of blocking queue

Blocking: must block / have to block
The blocking queue is a queue. Its role in the data structure is shown in the following figure:

When the queue is empty, the operation of getting elements from the queue will be blocked
When the queue is full, adding elements from the queue will be blocked

Threads trying to get elements from an empty queue will be blocked until other threads insert new elements into the empty queue

Threads trying to add new elements to a full queue will be blocked until other threads remove one or more elements from the queue or completely empty, freeing the queue and adding new elements later

Usefulness of blocking queues

In the field of multithreading: the so-called blocking will suspend the thread (i.e. blocking) in some cases. Once the conditions are met, the suspended thread will be automatically aroused

Why BlockingQueue
The advantage is that we don't need to care when we need to block the thread and wake up the thread, because all this is done by BlockingQueue

Before the release of concurrent package, in the multithreaded environment, * * each programmer must control these details by himself, especially considering * * efficiency and thread safety, which will bring great complexity to our program.

Class diagram of blocking queue

The following three most commonly used

  • Synchronous queue: a blocking queue that does not store elements, that is, a queue of individual elements.
  • ArrayBlockingQueue: a bounded blocking queue composed of an array structure.
  • LinkedBlockingQueue: a bounded (but the default size is integer.MAX_VALUE) blocking queue composed of a linked list structure.

BlockingQueue core method

Throw exceptionWhen the blocking queue is full, add ing elements to the queue will throw IllegalStateException:Queue full. When the blocking queue is empty, removing elements to the queue will throw NoSuchElementException
Special valueInsert method, success or failure, false
Remove the method and successfully return the elements out of the queue. If there is no element in the queue, null will be returned
Always blockedWhen the blocking queue is full, the producer thread continues to put elements into the queue, and the queue will block the producer thread until the put data or response interrupt exits
When the blocking queue is empty, the consumer thread attempts to take an element from the queue, and the queue will block the consumer thread until the queue is available
Timeout exitWhen the blocking queue is full, the queue will block the producer thread for a certain period of time. After the time limit is exceeded, the producer thread will exit

Blocking queue code

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/09    8:56
 * @Version:1.0
 *
 * Queue: first in, first out, FIFO, which is equivalent to queuing in the canteen
 *
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        /**
         * Throw exception
         * add:Add method
         * remove: Remove Method 
         * element: Check the method. Element is the element that returns the head of the queue. If there is no element in the queue, it returns null
         */

        // The add method is to add an element
 /*       System.out.println(queue.add("a"));
        System.out.println(queue.add("a"));
        System.out.println(queue.add("c"));
        // The queue is full
//        queue.add("d");  // Exception in thread "main" java.lang.IllegalStateException: Queue full
        System.out.println("========================================================================================");
        // element Is the element that returns to the head of the team
        System.out.println(queue.element());
        // remove Is to remove the element
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        // The queue is empty
//        System.out.println(queue.remove()); // Exception in thread "main" java.util.NoSuchElementException

        System.out.println("========================================================================================");
*/
        /**
         * Special value
         * offer: true is returned after successful insertion
         * poll: The removal operation (the operation of removing the head of the queue) successfully returns the elements of the queue. If there is no element in the queue, null will be returned
         * peek: Retrieves but does not delete the header of the queue represented by this queue (in other words, the first element of the deque),
         * If this deque is empty, null is returned.
         */
/*
        System.out.println(queue.offer("aa"));
        System.out.println(queue.offer("bb"));
        System.out.println(queue.offer("cc"));
//        System.out.println(queue.offer("cc")); // false

        System.out.println(queue.peek());
        System.out.println(queue.peek());
        System.out.println(queue.peek());

        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
//        System.out.println(queue.poll()); // null
        System.out.println("========================================================================================");
*/

  /*      queue.put("aa");
        queue.put("aa");
        queue.put("aa");
//        queue.put("aa");    //There are only three elements here, but you want to add four elements. Therefore, the last element will be blocked, which is equivalent to waiting for consumption

        queue.take();
        queue.take();
        queue.take();
//        queue.take();   // There are only three elements in this queue. Here we need to get the fourth one, so we have to wait until the producer produces the fourth one



*//*
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
*//*

         */
        System.out.println(queue.offer("aa"));
        System.out.println(queue.offer("aa"));
        System.out.println(queue.offer("aa"));
        // This is to wait for three seconds. If you don't have a cake, you'll leave. It's out of date
        System.out.println(queue.offer("aa", 3L, TimeUnit.SECONDS));


    }
}

Value Passing Mechanism of method parameters

  • Method must be called by its class or object to make sense. If the method contains parameters:
    • Formal parameter: parameter of method declaration
    • Argument: the parameter value actually passed to the formal parameter during method call

How can Java's argument values be passed into the method? There is only one way to pass parameters of methods in Java: value passing. That is, a copy (Replica) of the actual parameter value is passed into the method, and the parameter itself is not affected.

Formal parameter is a basic data type: pass * * data value * * of the argument basic data type variable to the formal parameter

Formal parameter refers to data type: pass * * address value * * of the data type variable referenced by the argument to the formal parameter

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/09    10:10
 * @Version:1.0
 */
public class TestTransferValue {

    public void changeValue1(int age) {
        age = 30;
    }

    public void changeValue2(Person person) {
        person.setName("xxxx");
    }

    public void changeValue3(String str) {
        str = "~~~~";
    }


    public static void main(String[] args) {
        TestTransferValue transferValue = new TestTransferValue();
        //  In one method, the basic type is only transmitted to the copy, and the original is not moved
        int age =20;
        transferValue.changeValue1(age);

        System.out.println("age = " + age);

        // The custom type Person is a reference type

        Person person = new Person("zsf");
        transferValue.changeValue2(person);
        System.out.println("personName ------------->" + person.getName());

        String str= "abc";
        transferValue.changeValue3(str);
        System.out.println("str = " + str);
    }


}	

Person class

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {

    private String name;
    private int age;

    public Person(String name) {
        this.name = name;
    }
}

ThreadPool thread pool

Why thread pool

example:
10 years ago, a single core CPU computer, fake multithreading, like a circus clown playing multiple balls, the CPU needed to switch back and forth.
Now it is a multi-core computer. Multiple threads run on independent CPU s without switching, and the efficiency is high.

Advantages of thread pool:
As long as the work done by the thread pool is to control the number of running threads, * * put the tasks in the queue during processing, * * and then start these tasks after the threads are created, * * if the number of threads exceeds the maximum number, the threads exceeding the number will wait in line, * * and then take the tasks out of the queue for execution after other threads finish execution.

**Its main features are: thread reuse; Control the maximum concurrent number; Manage threads**

First: reduce resource consumption. Reduce the consumption caused by thread creation and destruction by reusing the created threads.
Second: improve the response speed. When the task arrives, the task can be executed immediately without waiting for the thread to be created.
Third: improve the manageability of threads. Threads are scarce resources. If they are created without restrictions, they will not only consume system resources, but also reduce the stability of the system. Using thread pool can carry out unified allocation, tuning and monitoring.

How to use thread pool

Architecture description

The thread pool in Java is implemented through the Executor framework, which uses Executor, Executors, ExecutorService and ThreadPoolExecutor

Coding implementation

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/09    14:10
 * @Version:1.0
 *
 * Thread pool
 *
 */
public class MyThreadPoolDemo {

    public static void main(String[] args) {

        // =================First kind=================
        // Executors is a tool class of thread pool
        // newFixedThreadPool creation 	 A thread pool that reuses a fixed number of threads running from a shared unbounded queue.
        // Create a thread pool and set the processing window (initial thread) to 5
        // A pool of 5 threads is similar to a bank with 5 acceptance windows
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        // =================Second=================
        // The thread pool has only one working thread. Similarly, the bank has only one processing window
        //  ExecutorService threadPool = Executors.newSingleThreadExecutor();
        // There are N threads in a pool, and there are multiple threads in a pool, which is similar to the fact that a bank has N acceptance windows, which is related to the events executed by each thread
        // Create a thread pool that creates new threads as needed, but reuses previously constructed threads when available.


        // =================Third=================
        ExecutorService threadPool = Executors.newCachedThreadPool();

        try {
            for (int i = 1; i <= 10; i++) {
                threadPool.submit(() -> {

                    System.out.println(Thread.currentThread().getName() + "\t Processing completed");
                });
                // Pause for 1 second to simulate the processing time
//                TimeUnit.SECONDS.sleep(1);

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // Close thread pool
            threadPool.shutdown();
        }
    }
}

Underlying principle

In fact, the bottom layer is to create a ThreadPoolExecutor, then pass in different types of parameters and call different constructors

/**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters and default thread factory and rejected execution handler.
 * It may be more convenient to use one of the {@link Executors} factory
 * methods instead of this general purpose constructor.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @param maximumPoolSize the maximum number of threads to allow in the
 *        pool
 * @param keepAliveTime when the number of threads is greater than
 *        the core, this is the maximum time that excess idle threads
 *        will wait for new tasks before terminating.
 * @param unit the time unit for the {@code keepAliveTime} argument
 * @param workQueue the queue to use for holding tasks before they are
 *        executed.  This queue will hold only the {@code Runnable}
 *        tasks submitted by the {@code execute} method.
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

[the external chain image transfer fails, and the source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-cjmjinas-1643028768967) (C: \ users \ losser \ appdata \ roaming \ typora user images \ image-20220109181909648. PNG)]

Several important parameters of thread pool

  1. corePoolSize: the number of resident core threads in the thread pool

  2. maximumPoolSize: the thread pool can accommodate simultaneous
    The maximum number of threads to execute. This value must be greater than or equal to 1

  3. keepAliveTime: the survival time of redundant idle threads
    When the number of threads in the current pool exceeds corePoolSize, when idle time
    When keepAliveTime is reached, redundant threads are destroyed until
    There are only corePoolSize threads left

  4. Unit: the unit of keepAliveTime

  5. workQueue: task queue, a task submitted but not yet executed

  6. threadFactory: refers to the thread factory that generates the working threads in the thread pool,
    Used to create threads. Generally, the default is OK

  7. handler: reject policy, indicating that when the queue is full and the worker thread is greater than
    How to reject when it is equal to the maximum poolsize of the thread pool
    The policy of runnable that requests execution

Working principle of thread pool

The steps are 1 to 4 below

The corePoolSize in the figure below is 2 and the maximumPoolSize is 5. When two customers (1 and 2 threads) come to handle business at the beginning, the two today windows can handle it. When the following 3, 4 and 5 come again, the BlockingQueue is just full (5); Then the lobby manager will call other employees to be on duty and expand to the maximum poolsize (that is, expand to the maximum number of threads 5). When three more threads (6, 7 and 8) come in later, it will enter the blocking queue, and the waiting area is full. However, if another customer (9) comes in at this time, he will inform the bank that today's business is busy and go to other outlets to handle business (here it is handled according to the rejection strategy). If keepAliveTime is set, the newly added and expanded employees will return to the original corePoolSize after a long time of keepAliveTime after processing all businesses

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-u6bfut4k-1643028768969) (C: \ users \ losser \ appdata \ roaming \ typora user images \ image-20220109195333905. PNG)]

[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-fzgqekky-1643028768969) (C: \ users \ losser \ appdata \ roaming \ typora \ typora user images \ image-20220109201025539. PNG)]

The following are important:

  1. After creating the thread pool, start waiting for requests.

  2. When calling the * * execute() * * method to add a request task, the thread pool will make the following judgment:

     2.2 If the number of running threads is less than**corePoolSize**,Then create a thread to run the task immediately;
    
     2.2 If the number of running threads is greater than or equal to**corePoolSize**,Then put the task in the queue;
    
     2.3 If the queue is full and the number of running threads is less than**maximumPoolSize**,Then you still need to create a non core thread to run the task immediately;
    
     2.4 If the queue is full and the number of running threads is greater than or equal to**maximumPoolSize**,Then the thread pool will start the saturation rejection policy to execute.
    

When a thread completes a task, it will take a task from the queue to execute.

4 when a thread has nothing to do for more than a certain time (keepAliveTime), the thread will judge:

If the number of currently running threads is greater than corePoolSize, the thread will be stopped.

Therefore, after all tasks of the thread pool are completed, it will eventually shrink to the size of corePoolSize.

Which thread pool is used? If reasonable parameters are set in production

Rejection policy of thread pool

What is it?

The waiting queue is full and there are no more new tasks

At the same time, the max thread in the thread pool has reached and cannot continue to serve new tasks.

At this time, we need the rejection policy mechanism to deal with this problem reasonably.

JDK built-in rejection policy

  • Abortpolicy (default): throw the RejectedExecutionException exception directly to prevent the system from running normally
  • CallerRunsPolicy: "caller run" is a kind of adjustment mechanism, which neither discards the task nor
    Instead of throwing an exception, some tasks are fallback to the caller, reducing the traffic of new tasks.
  • Discard oldest policy: discards the longest waiting task in the queue, and then adds the current task to the queue
    Try submitting the current task again.
  • DiscardPolicy: this policy silently discards the tasks that cannot be handled, and does not handle them or throw exceptions.
    If you allow tasks to be lost, this is the best strategy.

The above built-in rejection policies all implement the RejectedExecutionHandle interface

Which of the three single / fixed / variable methods of creating thread pools is more useful in work? Super pit

  1. The answer is not to use any of them. We can only use custom ones in our work
  2. JDK in Executors has been provided to you. Why not?

Review of Java 8 flow computing

Functional interface

java.util.function

Four functional interfaces of java built-in core

Pseudo code:

//R apply(T t); Functional interface, one parameter, one return value
Function<String,Integer> function = t ->{return t.length();};
System.out.println(function.apply("abcd"));

//boolean test(T t); Assertive interface, a parameter, returns Boolean
Predicate<String> predicate = t->{return t.startsWith("a");};
System.out.println(predicate.test("a"));

// void accept(T t); Consumer interface, a parameter with no return value
Consumer<String> consumer = t->{
    System.out.println(t);
};
consumer.accept("javaXXXX");

//T get();  Supply type interface, no parameter, return value
Supplier<String> supplier =()->{return UUID.randomUUID().toString();};
System.out.println(supplier.get());
 

Stream stream

What is flow

What is a stream?
Is a data channel, which is used to operate the element sequence generated by the data source (set, array, etc.).
"Set is about data, flow is about calculation!"

characteristic

  1. Stream itself does not store elements
  2. Stream does not change the source object. Instead, they return a new stream that holds the result.
  3. Stream operations are deferred. This means they wait until they need results.

stage

  1. Create a Stream: a data source (array, collection)
  2. Intermediate operation: an intermediate operation that processes data source data
  3. Termination operation: a termination operation that executes the intermediate operation chain and produces results

Process:

Source = > intermediate pipeline = > result

Code example:

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/10    13:15
 * @Version:1.0
 *
 *  (lambda Several steps of expression:
 *1 , Copy parentheses (if there is only one parameter, the parameter type and parentheses can be omitted)
 *          2,Write dead right arrow
 *          3/Landing brace
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
// Support chain call
@Accessors(chain = true)
class User {

    // Chain programming + flow computing

    private Integer id;
    private String userName;
    private Integer age;
}

/**
 * Title: please find out the users who meet the following conditions at the same time according to the given data, that is, all the following conditions are met
 * Even ID, age greater than 24, user name capitalized and user name alphabetical inverted
 * Output only one user name
 */
public class StreamDemo {
    public static void main(String[] args) {


        User u1 = new User(11,  "a", 23);
        User u2 = new User(12, "b", 24);
        User u3 = new User(13, "c", 22);
        User u4 = new User(14, "d", 28);
        User u5 = new User(16, "e", 26);

        // Convert an array to a list collection
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        List<User> users = new ArrayList<>();
        list.stream().filter(user -> {
            // Filter out even id
            return user.getId() % 2 == 0;
        }).filter(user -> {
            // Filter those older than 24
            return user.getAge() > 24;
            // Map is a map
            // sorted is the sort of stream. The default is ascending
        }).map(user -> user.getUserName().toUpperCase()).sorted((o1, o2) -> {
            // Descending order
            return -o1.compareTo(o2);
            // limit is similar to that in MySQL, which limits the number forEach traversal, and then add
        }).limit(1).forEach(System.out::println);


        list.stream().sorted((o1, o2) -> {
            return -1;
        });

/*        for (User user : list) {
            if (user.getId() % 2 == 0 && user.getAge() > 24) {
                user.setUserName(user.getUserName().toUpperCase());
                users.add(user);
            }
        }*/


    }

}

Branch merge framework

principle

Fork: break up a complex task and make it small
Join: merge the results of the split task

Related classes

It is related to executor and thread pool

ForkJoinPool

Branch merge pool = > thread pool

ForkJoinTask

ForkJoinTask analogy = > futuretask

RecursiveTask

Recursive task: a task that can be called recursively (by yourself) after inheritance

Pseudo code

 
 class Fibonacci extends RecursiveTask<Integer> {
   final int n;
   Fibonacci(int n) { this.n = n; }
   Integer compute() {
     if (n <= 1)
       return n;
     Fibonacci f1 = new Fibonacci(n - 1);
     f1.fork();
     Fibonacci f2 = new Fibonacci(n - 2);
     return f2.compute() + f1.join();
   }
 }

Usage code of ForkJoin

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/10    22:05
 * @Version:1.0
 * ForkJoin frame
 * Divide and conquer algorithm is used
 * Basic review:
 * Abstract classes are inherited
 * The interface is implemented
 */
class MyTask extends RecursiveTask<Integer> {

    private static final Integer ADJUST_VALUE = 10;

    public int begin;
    public int end;
    public int result;

    public MyTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if (end - begin <= ADJUST_VALUE) {
            // When the value difference is less than or equal to 10, it will be accumulated directly
            for (int i = begin; i <= end; i++) {
                result += i;
            }
        } else {
            int middle = (begin + end) / 2;
            // Here is recursion
            // The first task is added from begin to the middle
            MyTask task01 = new MyTask(begin, middle);
            // The second task is added from the middle to the end
            MyTask task02 = new MyTask(middle + 1, end);
            // Come back and call compute
            task01.fork();
            task02.fork();
            // When is done returns the calculation result.
            result = task01.join() + task02.join();
        }
        return result;
    }
}

public class ForkJoinDemo {

    public static void main(String[] args) {
        // Create forkjoin pool
        ForkJoinPool forkJoinPool = null;
        try {
            MyTask myTask = new MyTask(0, 100);

            forkJoinPool = new ForkJoinPool();

            // Computing task
            ForkJoinTask<Integer> task = forkJoinPool.submit(myTask);
            // Get the result of calculation
            Integer integer = task.get();
            System.out.println("integer = " + integer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (forkJoinPool != null) {
                forkJoinPool.shutdown();
            }
        }

    }

}

Asynchronous callback completable future

CompletableFuture

This paragraph, which sounds very confusing, is a bit similar to the asynchronous request of ajax

/**
 * Created with IntelliJ IDEA.
 * @Author: pzx
 * @Date: 2022/01/10    22:47
 * @Version:1.0
 */
public class CompletableFutureDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // No return value
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t no return value");
        });
        completableFuture.get();


        // Asynchronous callback with return value supplyAsync supplied functional interface
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t completableFuture2");
//            return 10 / 0;
            return 1024;
        });
        //  The whenComplete method returns a new completable future. When the completable future is completed,
        //  whenComplete(
        //        BiConsumer<? super T, ? super Throwable> action)
        Integer result = completableFuture2.whenComplete((a, b) -> {
            // Take this step without exception
            System.out.println("a = " + a);
            // null here means there is no exception
            System.out.println("b = " + b);
            //The result is that the exception triggers the given function of this completable future to complete the special function;
            // Otherwise, if this completable future completes normally, the returned completable future will also complete normally with the same value.
        }).exceptionally(f -> {
            //  CompletableFuture<T> exceptionally(
            //        Function<Throwable, ? extends T> fn)

            // This step will be taken when there is an exception
            System.out.println(f.getMessage());
            return 444;
        }).get();

        System.out.println(result);

    }

}

Keywords: Java JUC

Added by ToeBee on Tue, 25 Jan 2022 15:04:49 +0200