A brief talk about Juc concurrent programming — next

ReadWriteLock read-write lock

ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writes

Read and write locks are mutually exclusive and only one can be running at a time

It can be read by multiple threads simultaneously

Only one thread can write when writing

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/ * * *@Author if
 * @Description: Read/write lock: mainly prevents multiple threads from writing and reading at the same time resulting in illusory * read lock: shared lock * write lock: exclusive lock *@Date 2021-11-06 上午 12:31
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyCache myCache=new MyCache();

        //10 threads do write only
        for(int i=1; i<=10; i++){final int temp=i;
            new Thread(()->{
                myCache.put(temp+"",temp);
            },"write"+i).start();
        }


        //10 threads only do reads
        for(int i=1; i<=10; i++){int temp=i;
            new Thread(()->{
                myCache.get(temp + "");
            },"read"+i).start(); }}// Custom cache
    static class MyCache{
        private volatile Map<String,Object> map=new HashMap<>();
        // read/writeLock, you can use writeLock and readLock to obtain write and read locks, and then lock
        private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();

        // When writing, only one thread is expected to operate
        public void put(String key,Object value){
            // get writeLock writeLock from readWriteLock
            Lock writeLock = readWriteLock.writeLock();
            // Write lock to lock
            writeLock.lock();
            try{
                System.out.println(Thread.currentThread().getName()+"Write key ="+key);
                map.put(key,value);
                System.out.println(Thread.currentThread().getName()+"Write complete");
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                // Write the lock to unlock itwriteLock.unlock(); }}// Each thread is expected to read
        public void get(String key){
            Lock readLock = readWriteLock.readLock();
            readLock.lock();
            try{
                System.out.println(Thread.currentThread().getName()+"Read key ="+key+",value = "+map.get(key));
            }catch(Exception e){
                e.printStackTrace();
            }finally{ readLock.unlock(); }}}}Copy the code

Block queue BlockingQueue

When are blocking queues used: multi-threaded concurrent processing, thread pools

It’s a little bit like the producer-consumer problem

  • Write: If the queue is full, it must block and wait
  • Fetch: If the queue is empty, it must block to wait for production

BlockingQueue’s four sets of apis

way An exception is thrown No exception is thrown and a value is returned Block waiting for Timeout waiting for
add add() offer() put() offer(,,)
remove remove() poll() take() poll(,)
Determine the head of the queue element() peek

Add operations are nulled, so null is not allowed

	checkNotNull(e);

	private static void checkNotNull(Object v) {
        if (v == null)
            throw new NullPointerException();
    }
Copy the code

1. Throw an exception

Once the queue size is set, each of these operations throws an exception

  • Queue full before adding:IllegalStateException: Queue full
  • Queue empty then fetch:NoSuchElementException
    // throw an exception
    public static void throwException(a){
        // The capacity parameter indicates the queue size
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));

        // the first queue is a
        System.out.println("blockingQueue.element() = " + blockingQueue.element());

        // Set the queue size to 3 and throw an exception when a fourth element is added
        //java.lang.IllegalStateException: Queue full
// System.out.println(blockingQueue.add("d"));

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());

        / / to empty the queue, then remove Java exception. Util. NoSuchElementException
// System.out.println(blockingQueue.remove());

        // NoSuchElementException is thrown after the queue is emptied
        System.out.println("blockingQueue.element() = " + blockingQueue.element());
    }
Copy the code

The situation here is exactly the same as the exception for a normal LinkedList queue

Take a look at the source code for ArrayBlockingQueue

You can see that ArrayBlockingQueue calls its parent AbstractQueue’s add() method

The Add method calls the Offer method and actively throws an exception if the add fails (the Offer method of implement BlockingQueue).

    public boolean add(E e) {
        return super.add(e);
    }

	
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
Copy the code

The Element method also calls the peek method

Is the empty-handed throwing anomaly

    public E element(a) {
        E x = peek();
        if(x ! =null)
            return x;
        else
            throw new NoSuchElementException();
    }

    public E peek(a) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return itemAt(takeIndex); // null when queue is empty
        } finally{ lock.unlock(); }}Copy the code

2. No exception is thrown and a value is returned

    //2. No exception is thrown and a value is returned
    public static void noExceptionAndReturn(a){
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));

        // the first queue is a
        System.out.println("blockingQueue.peek() = " + blockingQueue.peek());

        // Full queue returns false
        System.out.println(blockingQueue.offer("d"));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());

        // Queue empty, return null
        System.out.println(blockingQueue.poll());

        // Queue empty, no queue head, return null
        System.out.println("blockingQueue.peek() = " + blockingQueue.peek());
    }
Copy the code

3. Block and wait

    //3
    public static void blockWait(a) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println("Began to put");

        new Thread(()->{
            try {
                blockingQueue.put("a");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                blockingQueue.put("b");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                blockingQueue.put("c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // Give 1 second for the thread to fill the queue
        TimeUnit.SECONDS.sleep(1);

        // When the queue is full, the blocking thread will be blocked from output
        new Thread(()->{
            try {
                blockingQueue.put("d");
                System.out.println(Thread.currentThread().getName()+"Put out");
            } catch(InterruptedException e) { e.printStackTrace(); }},"Blocked thread").start();

        // The emergency thread takes an element out of the Bq, leaving a queue empty, and the last blocking thread can complete the put
        new Thread(()->{
            try {
                blockingQueue.take();
                System.out.println("Open another one."+Thread.currentThread().getName()+"Take it out, can the blocking thread output? You can");
            } catch(InterruptedException e) { e.printStackTrace(); }},"Emergency thread").start();

        System.out.println("Can the main thread output? Can output, no impact, because the blocking thread is the one above the blocking thread.");
        System.out.println("If put on the main thread, it will block here as well \n====================");
    }
Copy the code

Because put() and take() use the Condition monitor, calls to await and single achieve precise sleep and wakeup

Here’s the parsing

The member variable condition

Mentioned above, not repeated here

private final Condition notFull;

Put method

While (count == items.length) queue is full, notfull.await (); Thread waiting

Notempty.signal () in enqueue; Wake up the blocked Take thread

    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally{ lock.unlock(); }}private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }
Copy the code

Take method

While (count == 0) queue empty, notempty.await (); Thread waiting

In the dequeue method, notfull.signal (); Wake up the blocked PUT thread

    public E take(a) throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally{ lock.unlock(); }}private E dequeue(a) {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] ! = null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if(itrs ! =null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }
Copy the code

4. Wait time out

The analogy with blocking and waiting is easy to understand

Blocking wait means that the queue will wait until another thread is operating on it

Timeout wait Waits for a specified period of time. Timeout is abandoned

    //4
    public static void outTimeWait(a) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        // The offer and poll methods are used again, but this time with parameters

        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");

        System.out.println("Normal offer method, immediately abandon ->"+blockingQueue.offer("d"));

        // Wait 3 seconds, if the block is still blocked, abort
        // If the method takes no parameters, it will be abandoned immediately
        System.out.println("Offer method with input, wait and discard ->"+blockingQueue.offer("d".3,TimeUnit.SECONDS));

        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll();

        System.out.println("Normal poll method, discard immediately ->" + blockingQueue.poll());

        // Wait 3 seconds, still blocked, aborted
        System.out.println("Poll method with parameters, wait and abandon ->" + blockingQueue.poll(3,TimeUnit.SECONDS));
    }
Copy the code

Let’s look at the source code with parameter offer

long nanos = unit.toNanos(timeout); The timeout was obtained

If (nanos <= 0) determines whether the timer ends

  • Nacos <0, countdown ends,return false;Give up waiting to come straight back (disheartened)
  • Nacos >0 and countingnanos = notFull.awaitNanos(nanos);Call condition’s awaitNanos to continue the timed wait (hopefully)
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(e);
            return true;
        } finally{ lock.unlock(); }}Copy the code

SynchronizedQueue

Unlike other BlockingQueues, it is not used to store elements

Once an element is put, it must be taken out, or else it waits (equivalent to a one-space BlockingQueue?).

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @DescriptionUnlike other BlockingQueues, synchronous queues are not used to store elements. Once an element is put, it must be taken out, or else it will wait. *@Date 2021-11-06 下午 04:42
 */
public class SynchronizedQueueTest {
    public static void main(String[] args) {
        BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"put a");
                synchronousQueue.put("a");

                System.out.println(Thread.currentThread().getName()+"put b");
                synchronousQueue.put("b");

                System.out.println(Thread.currentThread().getName()+"put c");
                synchronousQueue.put("c");
            } catch(InterruptedException e) { e.printStackTrace(); }},"Thead-put").start();

        new Thread(()->{
            try {
                // Give him some time to put
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"take = " + synchronousQueue.take());

                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"take = " + synchronousQueue.take());

                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName()+"take = " + synchronousQueue.take());
            } catch(InterruptedException e) { e.printStackTrace(); }},"Thread-take").start(); }}Copy the code

The thread pool

Normally, threads need to be created and destroyed. It’s a waste of resources and time

Pooling technology: prepare some resources in advance, and if someone wants to use them, come to me to take them and return them to me after using them

Benefits of thread pools:

  1. Reduce resource consumption
  2. Increase the speed of response
  3. Convenient management

Thread reuse, can control the maximum number of concurrent, management threads

Thread pool: 3 methods, 7 parameters, 4 rejection policies **

Ali Java specification about thread pools writes

Thread pools are not allowed to be created by Executors, but by ThreadPoolExecutor

In this way, students can be more clear about the running rules of the thread pool and avoid the risk of resource exhaustion.

* If the thread pool object returns by Executors, it has the following disadvantages:

  • FixedThreadPoolandSingleThreadPool:
    • The allowed request queue length isInteger.MAX_VALUE, may pile up a large number of requests, resulting in OOM
  • CachedThreadPool andScheduledThreadPool:
    • The number of threads allowed to create isInteger.MAX_VALUE, may create a large number of threads, resulting in OOM

OOM: Out of Memory Memory overflow

Executors are also new’s ThreadPoolExecutor, but specify some parameters by default

Three methods

1. SingleThreadExecutor

        //Single creates a Single thread for processing
        ExecutorService service1 = Executors.newSingleThreadExecutor();
        try{
            for(int i=1; i<=5; i++){ service1.execute(()->{ System.out.println(Thread.currentThread().getName()+"In execution"); }); }}catch(Exception e){
            e.printStackTrace();
        }finally{
            service1.shutdown();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("========== service1 closed ==========");
        }
Copy the code

The execution result

Pool-1-thread-1 Executing pool-1-thread-1 Executing pool-1-thread-1 Executing pool-1-thread-1 Executing pool-1-thread-1 Executing Pool-1-thread-1 Executing Pool-1-thread-1 Executing

FixedThreadPool FixedThreadPool

        //Fix fixed, according to the argument nThreads, to create a fixed thread pool size
        ExecutorService service2 = Executors.newFixedThreadPool(5);
        try{
            for(int i=1; i<=10; i++){ service2.execute(()->{ System.out.println(Thread.currentThread().getName()+"In execution"); }); }}catch(Exception e){
            e.printStackTrace();
        }finally{
            service2.shutdown();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("========== service2 closed ==========");
        }
Copy the code

The execution result

Pool-2 thread-2 Pool-2 thread-2 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-3 Pool-2 thread-4 Pool-2 thread-5 Pool-2 thread-5

3, Cache thread pool CachedThreadPool

        // Retractable, strong when strong, weak when weak
        ExecutorService service3 = Executors.newCachedThreadPool();
        try{
            for(int i=1; i<=10; i++){// All pool-3-thread-1 threads are executed
                // It is possible to infer that multiple threads are pooled only when there is a large number of concurrent requests
// Thread.sleep(1);
                service3.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"In execution"); }); }}catch(Exception e){
            e.printStackTrace();
        }finally{
            service3.shutdown();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("========== service3 closed ==========");
        }
Copy the code

The execution result

Pool in carrying out the pool – 3 – thread – 1-3 – thread – 2 in the execution of the pool – 3 – thread – 3 in carrying out the pool – 3 – thread – in the execution of the pool – 3-4 thread – 5 in the execution of the pool – 3 – thread – 7 in the execution Pool-3-thread-8 Pool-3-thread-9 Pool-3-thread-2 Pool-3-thread-6 Pool-3-thread-6 Pool-3-thread-8 Pool-3-thread-9 Pool-3-thread-2 Pool-3-thread-6

Seven parameters

If you look at the source code, you can see that one of the three methods that call Executor is the ThreadPoolExecutor of New

    public static ExecutorService newSingleThreadExecutor(a) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1.1.0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    public static ExecutorService newCachedThreadPool(a) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
Copy the code

They just default to some fixed parameter, such as maximumPoolSize= integer.max_value

As the saying goes, what fits is best, and sometimes the default argument is not always the right one, so the Ali Java specification lets us call the native thread pool to help classes create a thread pool

	public ThreadPoolExecutor(intCorePoolSize, // The initial core thread pool sizeintMaximumPoolSize, // Maximum thread pool size (not enough core threads, add non-core threads)longKeepAliveTime, // keepAliveTime, // keepAliveTime, // keepAliveTime, // keepAliveTime. BlockingQueue<Runnable> workQueue,// ThreadFactory ThreadFactory,// ThreadFactory, Use the default RejectedExecutionHandler handler.AbortPolicy is used when the request exceeds the maximum thread capacity and the blocking queue is full.
Copy the code

Description of thread pool operation principle

As you can see, when a request is made to the thread pool, the thread pool first processes the request according to the core thread created at initialization. When the core thread is in use, subsequent requests are put into the blocking queue

When the core threads are all processing and the blocking queue is full, non-core threads continue to be created based on maximumPoolSize, the maximum thread pool size

KeepAliveTime and Unit determine how long non-core threads can live without business calls

Non-core threads are retracted after keepAliveTime ends

If all threads (core and non-core) are taken and the blocking queue is full, the rejection policy is adopted

The default rejection policy is AbortPolicy, which does not accept anything beyond the tolerance and throws an exception

    private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

	/ / RejectedExecutionHandler class is the interface class, there are four implementation class, we call it four refused to strategy
	public static class CallerRunsPolicy implements RejectedExecutionHandler
	public static class AbortPolicy implements RejectedExecutionHandler
	public static class DiscardPolicy implements RejectedExecutionHandler
	public static class DiscardOldestPolicy implements RejectedExecutionHandler
Copy the code

Simple code implementation

We initialize two core threads with a maximum of five, a timeout of five seconds, a blocking queue size of three, a default thread factory, and an abort policy

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @DescriptionCall native ThreadPoolExecutor to create a thread pool *@Date 2021-11-07 下午 03:30
 */
public class MyPool {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2.5.5,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        try{
            for(int i=1; i<=9; i++){ threadPoolExecutor.execute(()->{ System.out.println(Thread.currentThread().getName()+"In operation"); }); }}catch(Exception e){
            e.printStackTrace();
        }finally{ threadPoolExecutor.shutdown(); }}}Copy the code

The results show that there are two core threads running at this time

Because 3 out of 5 requests are put in the blocking queue

Pool-1-thread-1 Is running Pool-1-thread-2 is running Pool-1-thread-1 is running Pool-1-thread-2 is running Pool-1-thread-1 is running

At this point, if we increase the number of loops to 8, we can see that 3,4,5 more threads have been created, which is the maximum number of non-core threads (5-2=3), which means that 3 more non-core threads can be created to solve the problem

Pool-1-thread-2 Is running Pool-1-thread-5 is running Pool-1-thread-3 is running Pool-1-thread-5 is running Pool-1-thread-1 is running Pool-1-thread-2 is running Pool-1-thread-3 Is running Pool-1-thread-4 is running

You know, we have a maximum of five threads and three positions in the queue, so we can have a total of eight requests, so what happens when we loop nine times?

Pool-1-thread-2 Is running Pool-1-thread-4 is running Pool-1-thread-3 is running Pool-1-thread-1 is running Pool-1-thread-3 is running Pool-1-thread-4 is running – the thread pool – 1-5 are running – the thread pool – 1-2 is running Java. Util. Concurrent. RejectedExecutionException story (omitted)

Yes, when the concurrent request processing not to come over, we select the refuse strategy is directly refuse the request and AbortPolicy suspended RejectedExecutionException exception

Four rejection strategies

1. AbortPolicy AbortPolicy

When concurrent requests processing not over AbortPolicy suspension strategies will directly discarded tasks and throw an exception. Java util. Concurrent. RejectedExecutionException

2. The caller runs CallerRunsPolicy

Go back to where you came from

The thread pool says, “I don’t have the resources to process your request anymore. Whoever let you in will do it.

Pool-1-thread-1 Running Main Running Pool-1-thread-1 Running Pool-1-thread-3 Running Pool-1-thread-2 Running Pool-1-thread-3 running Pool-1-thread-1 Is running Pool-1-thread-5 is running Pool-1-thread-4 is running

3, DiscardPolicy

If there are not enough resources, drop the task directly without throwing an exception.

DiscardOldestPolicy DiscardOldestPolicy

Discard old tasks in the thread and add new ones

Delete the first queued task and try to join the queue later

When a task is rejected, the oldest task in the queue is discarded and the new task is added to the queue

In the rejectedExecution command, remove the task to be added first from the task queue, leave a position vacant, and execute the execute method again to add the task to the queue

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
Copy the code

How to define the maximum number of threads?

CPU bound

CPU intensive, also called computing intensive, refers to the system hard disk and memory performance is much better than CPU

At this time, most of the system operation is CPU Loading100%, THE CPU needs to read/write I/O(disk/memory), I/O can be completed in a very short time, but the CPU has a lot of operations to process, CPU Loading is very high. On a multiprogramming system, a program that spends most of its time doing CPU actions, such as calculations and logical decisions, is called CPU bound

Cpu-bound programs tend to use a lot of CPU. This may be because the task itself does not require much access to the I/O device, or because the program is multithreaded and therefore blocks out waiting for I/O

Generally, the number of threads is set to:

** Number of threads = number of CPU cores +1 **(Modern cpus support hyperthreading, using idle wait)

I/O bound

I/O intensive means that the CPU performance of the system is much better than that of hard disks and memory. In this case, the CPU is waiting for I/ OS (hard disks and memory) to read or write, and the CPU Loading is not high.

I/O Bound’s programs tend to hit their performance limits and still have low CPU usage. This is probably because the task itself requires a lot of I/O operations and the Pipeline didn’t do a very good job of utilizing processor power.

Generally, the number of threads is set to:

** Number of threads = Total CPU cores * 2 +1 **

The maximum number of processors available for a Java virtual machine must not be less than one

Runtime.getRuntime().availableProcessors()
Copy the code

You can also view it in Task Manager -> Performance -> CPU -> Logical Processor

Four functional interfaces

Programmers of the new era need to master: lambda expressions, chain programming, functional interfaces, Stream computing

What is a functional interface

An interface that has only one method

Typical examples are Runnable and Callable interfaces

You can see that they are annotated by @functionalinterface, which is literally called a FunctionalInterface

@FunctionalInterface
public interface Runnable {
    public abstract void run(a);
}

@FunctionalInterface
public interface Callable<V> {
    V call(a) throws Exception;
}
Copy the code

1, Function interface

I have an input T and an output R

@FunctionalInterface
public interface Function<T.R> {
    R apply(T t);
}
Copy the code
        // Plain calls, using anonymous inner classes
        Function<String,String> function=new Function<String, String>() {
            @Override
            public String apply(String s) {
                returns; }}; System.out.println(function.apply("test"));

        //lambda
        Function<String,String> functionL= (str)->{returnstr; }; System.out.println(functionL.apply("lambda"));
Copy the code

It could even be easier

        / / more easily
        Function<String,String> functionLS= str-> str;
        System.out.println(functionLS.apply("simple"));
Copy the code

2. Predicate interfaces

If there is input, return Boolean

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
Copy the code
        Predicate<Integer> predicate = new Predicate<Integer>(){
            @Override
            public boolean test(Integer num) {
                return num.equals(1); }}; System.out.println(predicate.test(2));
        System.out.println(predicate.test(1));

        Predicate<Integer> predicateL= num-> num.equals(1);
        System.out.println("predicateL.test(1) = "+predicateL.test(1));
Copy the code

3. Consumer interface

Only input, no return value

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
Copy the code

Sout can even simplify system.out ::println

    Consumer<String> consumer= str-> System.out.println(str);
    consumer.accept("out");

	// Minimal version
    Consumer<String> consumerS= System.out::println;
    consumerS.accept("out");
Copy the code

4. Supplier interface Supplier

Only return values, no arguments

@FunctionalInterface
public interface Supplier<T> {
    T get(a);
}
Copy the code
        Supplier<String> supplier= ()-> "asd";
        System.out.println(supplier.get());
Copy the code

Stream computing

Is a stream an IO stream?

** is not!! ** This is an IO stream under the java.io package

The java.util package contains a Stream

import java.io.InputStream;
import java.util.stream.Stream;
Copy the code

What is Stream computing?

Big data = computing + storage

Storage: collections, mysql databases…

Calculation: stream

And there are many, many arguments in these streams that use functional interfaces

Without further ado, get right to the code

import java.util.Arrays;
import java.util.List;

/ * * *@Author if
 * @DescriptionThere are now 5 users to filter: * 1, ID must be even * 2, age must be greater than 23 * 3, username uppercase * 4, username alphabeticssorted backwards * 5, output only one user *@Date 2021-11-07 下午 06:12
 */
public class Test01 {
    public static void main(String[] args) {
        User user1 = new User(1."a".21);
        User user2 = new User(2."b".22);
        User user3 = new User(3."c".23);
        User user4 = new User(4."d".24);
        User user5 = new User(6."e".25);

        // Add five users to the list
        List<User> list= Arrays.asList(user1,user2,user3,user4,user5);
        // The calculation is passed to the stream
        list.stream()
                //ID must be even
                .filter(user-> user.getId()%2= =0)
                // Must be older than 23
                .filter(user-> user.getAge()>23)
                // Change the user name to uppercase
                .map(user -> user.getName().toUpperCase())
                // User name alphabetically sorted backwards (comparator.reverseOrder ())
                .sorted((u1,u2)->u2.compareTo(u1))
                // Output only one user
                .limit(1) .forEach(System.out::println); }}Copy the code

ForkJoin branch merge calculation

What is ForkJoin?

ForkJoin performs tasks in parallel in JDK1.7 for increased efficiency and large data volumes! Big Data: Map Reduce (Breaking big tasks into smaller ones)

ForkJoin nature: Divide and conquer

A big task is divided into many small tasks, and finally the results are summarized to get the solution

ForkJoin features: Work to steal

When THREAD B finishes earlier and A is not finished yet, B will take some work from A’s task to help share the pressure and improve efficiency

These are all two-ended queues that are maintained

How do I use ForkJoin

It is better to use it in the case of large data volume to improve efficiency, and it is better to directly use for loop in the case of small data volume

Direct access to the code, which feels like a recursion, adds the work queue mechanism of ForkJoin

There are three calls

  • public void execute(ForkJoinTask
    task), called directly with no return value
  • Public ForkJoinTask Submit (ForkJoinTask task)Task executionTo return toGet ForkJoinTask class
    • Public final V get(), which returns the ForkJoinTask get method to get the result
  • Public T invoke(ForkJoinTask task), invoke is executed
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

/ * * *@Author if
 * @Description: What is it
 * @Date 2021-11-07 下午 06:46
 */
public class ForkJoinDemo extends RecursiveTask<Long> {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start=1L;
        long end=10_0000_0000L;
        ForkJoinDemo forkJoinDemo = new ForkJoinDemo(start,end);
        ForkJoinPool forkJoinPool=new ForkJoinPool();
        // Execute the task, no return value
// forkJoinPool.execute(forkJoinDemo);
        ForkJoinTask submits the task, obtains the return value of the ForkJoinTask class, and then retrieves the result based on the class get
// ForkJoinTask
      
        submit = forkJoinPool.submit(forkJoinDemo);
      
// System.out.println("submit.get() = " + submit.get());
        // Invoke is invoked to get the return value directly, which is simpler than the previous method
        Long sum = forkJoinPool.invoke(forkJoinDemo);
        System.out.println("sum = " + sum);
    }

    private Long start;
    private Long end;
    private Long temp=10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute(a) {
        // When the value is smaller than the critical value, a normal loop is taken
        if((end-start)<temp){
            long sum=0L;
            for(longi=start; i<=end; i++){ sum+=i; }return sum;
        }
        // When the threshold is exceeded, fork-join is used
        long mid=(start+end)/2;
        // Divide it into two tasks
        ForkJoinDemo fj1=new ForkJoinDemo(start,mid);
        // Press the task queue
        fj1.fork();
        ForkJoinDemo fj2=new ForkJoinDemo(mid+1,end);
        fj2.fork();
        // Returns the result of the integration
        returnfj1.join()+fj2.join(); }}Copy the code

You can also use stream to parallel streams

        // Use stream to parallel streams
        Long sum = LongStream.rangeClosed(start, end)
                .parallel()
                .reduce(0,Long::sum);
        System.out.println("sum = "+sum1);
Copy the code

The asynchronous call

We use two methods of the CompletableFuture class, runAsync() and supplyAsync()

public class CompletableFuture<T> implements Future<T>, CompletionStage<T>
Copy the code

An asynchronous call with no return value

        // runAsync asynchronous call with no return value
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
            System.out.println("CompletableFuture.runAsync");
        });
        runAsync.get();

        System.out.println("= = = = = = = = = = = = = =");
Copy the code

An asynchronous call with a return value

Similar to Ajax and AXIos, the requested business is submitted with a success callback and a failure callback

The argument to the supplyAsync() method is a functional interface Supplier Supplier

@FunctionalInterface
public interface Supplier<T> {
    T get(a);
}
Copy the code

That is, where the business logic is executed

The processing of the business can then be determined by the success callback whenComplete and failure callback

The code is as follows, and the comments are very clear, so it’s arranged here to make it easier to see

  • The successful callback whenComplete is executed with or without an exception, so judge t and u to determine the code logic
    • If t is not null, the execution will be normal. If t is null, the execution will fail.
    • U is an error message. If u is null, it is normal. Otherwise, it is an exception message
    • No return value (because the return value 200 for successful business execution was already written in supplyAsync)
  • The failure callback is exceptionally, which is executed only if an exception occurs
    • Parameter e, usually Exception e
    • There are return values (usually different return values depending on the exception)
        // A runAsync asynchronous call with a return value
        // Return a CompletableFuture object, and get the final result
        CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> {
            // Execute the business logic
            System.out.println("CompletableFuture. Execute the business logic in the supplyAsync");
// int i=1/0;
            return 200;
        })
            // The successful callback is executed regardless of the exception, so judge t and u to determine the code logic
            .whenComplete((t, u) -> {
                // If t is not null, it is executed normally
                if(! Objects.isNull(t)&&Objects.isNull(u)){ System.out.println("t = " + t);// Return a normal result
                }else{
                    // If u is null, the execution is normal; otherwise, an exception message is displayed
                    System.out.println("Exception message u =" + u);// Error message}})// The failure callback is executed only if an exception occurs
            .exceptionally((e) -> {
                // The parameter is Exception e
// e.printStackTrace();
                System.out.println("Exception callback -> LLDB etMessage() =" + e.getMessage());
                return 400;
            });
        
        // Get successful/exception results
        System.out.println("result.get() = "+result.get());
Copy the code

The execution result

Normal execution

CompletableFuture. Execute the business logic in the supplyAsync t = 200. The result of the get () = 200

Execution failure

CompletableFuture. Execute the business logic in the supplyAsync exception information u = java.util.concurrent.Com pletionException: Java. Lang. ArithmeticException: / by zero anomaly correction – > um participant etMessage () = Java lang. ArithmeticException: / by zero

result.get() = 400

Understand the JMM

We’ve been dealing with JVMS, Java Virtual Machines, Java Virtual Machines

What is the JMM?

Java Memory Model

It is a nonexistent model, equivalent to a concept, a convention

Some synchronization conventions for the JMM

  1. The shared variable == must be flushed back to main memory immediately before the thread can be unlocked
  2. Before a thread locks, it must read the latest value from main memory into working memory!
  3. Locking and unlocking are the same lock

JVM design takes into account that if JAVA threads directly manipulate main memory each time they read and write variables, performance will be greatly affected. Therefore, each thread has its own working memory. Variables in working memory are a copy of main memory, and the thread reads and writes variables directly in working memory. You can’t directly manipulate variables in main memory. The problem with this, however, is that when a thread changes a variable in its own working memory, it is not visible to other threads, leading to thread-unsafe problems. Because the JMM has a set of standards to ensure that developers, when writing multithreaded programs, can control when memory is synchronized to other threads. Right

Let’s take a look at what the following two threads do to main memory

Memory interoperation

There are eight types of memory interaction operations, and virtual machine implementations must ensure that each operation is atomic and non-separable

(Exceptions are allowed for load, store, read, and write on some platforms for variables of type double and long.)

  • Lock: A variable that acts on main memory, marking a variable as thread-exclusive
  • Unlock: A variable that acts on main memory. It releases a locked variable so that it can be locked by another thread
  • Read: Acts on a main memory variable that transfers the value of a variable from main memory to the thread’s working memory for subsequent load action
  • Load: Variable acting on working memory, which puts a read operation variable from main memory into working memory
  • Use: Applies to variables in working memory. It transfers variables in working memory to the execution engine. This instruction is used whenever the virtual machine reaches a value that requires the variable to be used
  • Assign: A variable applied to working memory that places a value received from the execution engine into a copy of the variable in working memory
  • Store: Acts on a variable in main memory. It transfers a value from a variable in working memory to main memory for subsequent writes
  • Write: A variable in main memory that puts the value of a variable in main memory from the store operation in working memory

The JMM lays down the following rules for the use of these eight directives:

  • One of the read and load, store and write operations is not allowed to occur separately
    • If you use read, you must load; if you use store, you must write
  • Disallow a thread to discard its latest assign operation
    • That is, the main memory must be notified when the data of the working variable changes
  • A thread is not allowed to synchronize unassigned data from working memory back to main memory
  • A new variable must be created in main memory. Working memory is not allowed to use an uninitialized variable directly
    • Before performing use and store operations on variables, you must pass assign and load operations
  • Only one thread can lock a variable at a time. You must unlock the device for the same number of times
  • If a variable is locked, all of its values in working memory will be emptied. Before the execution engine can use the variable, the variable must be reloaded or assigned to initialize its value
  • You cannot unlock a variable if it is not locked. You cannot unlock a variable that is locked by another thread
  • Before an UNLOCK operation can be performed on a variable, it must be synchronized back to main memory

Volatile

Volatile is a Java keyword that provides a lightweight synchronization mechanism for Java virtual machines, but Volatile does not guarantee thread safety

  • Guaranteed visibility
  • Atomicity is not guaranteed
  • Disallow command reordering

1. Ensure visibility

Let’s look at this problem code

import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @Description: What is it
 * @Date 2021-11-08 下午 06:11
 */
public class JmmTest {
    private static int num=0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(num==0){

            }
            System.out.println("Thread terminated");
        }).start();

        TimeUnit.SECONDS.sleep(1);

        num=1;
        System.out.println("num = "+num); }}Copy the code

Will the “thread” end statement be printed at this point? Will not be

Because the thread does not know that num has been changed by the main thread, num in working memory is still 0, so it keeps looping

What if we added the keyword volatile to num?

private static volatile int num=0;

As you can see, the thread is finished

Num = 1

Process finished with exit code 0

2. Atomicity is not guaranteed

ACID refers to four characteristics that a database management system (DBMS) must have in order to ensure that a transaction is correct and reliable when data is written or updated:

  • atomic(Atomicity, or indivisibility)
    • All operations in a transaction either complete or do not complete and do not end up somewhere in the middle
  • consistency(consistency)
    • The integrity of the database is not compromised before and after a transaction
  • Isolation,(Isolation, also called independence)
    • The ability of a database to allow multiple concurrent transactions to read, write, and modify its data at the same time, and isolation prevents data inconsistencies due to cross-execution of multiple concurrent transactions **
      • Transaction isolation can be divided into different levels, including Read uncommitted, Read Committed, Repeatable Read and Serializable.
  • persistence(durability)
    • After a transaction, changes to the data are permanent and will not be lost even if the system fails

Let’s look at the following code

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

/ * * *@Author if
 * @Description: What is it
 * @Date 2021-11-08 下午 06:27
 */
public class VolatileTest {

    private static int num=0;
    private static volatile int vnum=0;

    public static void add(a){
        num++;
        vnum++;
    }

    private static int snum=0;
    public synchronized static void sAdd(a){
        snum++;
    }

    private static int lnum=0;
    private static Lock lock=new ReentrantLock();
    public static void lAdd(a){
        lock.lock();
        try{
            lnum++;
        }catch(Exception e){
            e.printStackTrace();
        }finally{ lock.unlock(); }}public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                    sAdd();
                    lAdd();
                }
            }).start();
        }

        while (Thread.activeCount()>2) {// The thread cedes the current time slice to another thread
            Thread.yield();
        }
        System.out.println("End, ordinary num ="+num+", volatile vnum ="+vnum);
        System.out.println("End,synchronized snum ="+snum);
        System.out.println("End,lock method lnum ="+lnum); }}Copy the code

It can be seen that neither normal num nor volatile vnum is the correct 20000 result

Lnum and snum in lock and synchronized methods are correct with 20,000 results

Num = 19986; vnum = 19986; snum = 20000; lNUM = 20000

Because volatile does not guarantee atomicity, num++ increment is not an atomic operation

Decompile view

The javap -c volatiletest. class command is used to decompile the class file

  public static void add(a);
    Code:
	// Get static num at the top of the stack
       0: getstatic     #2                  // Field num:I
    // Put constant 1 at the top of the stack
       3: iconst_1
    (num=num+1);
       4: iadd
    // The result at the top of the stack is assigned to static num
       5: putstatic     #2                  // Field num:I
       
    // Select * from vnum
       8: getstatic     #3                  // Field vnum:I
      11: iconst_1
      12: iadd
      13: putstatic     #3                  // Field vnum:I
      16: return
Copy the code

Atomic classes Atomic

How do you implement atomicity without using synchronized and lock?

Using Java. Util. Concurrent. Atomic package under the atomic classes

The bottom layers of these classes hook directly to the operating system! Modify values in memory! The Unsafe class is a very special existence!

The code for

import java.util.concurrent.atomic.AtomicInteger;

/ * * *@Author if
 * @Description: What is it
 * @Date2021-11-08 11:12 p.m. */
public class AtomicTest {
    private static AtomicInteger num=new AtomicInteger(0);

    public static void add(a){
        // Increment and return, CAS optimistic lock
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while(Thread.activeCount()>2){
            Thread.yield();
        }
        long endTime=System.currentTimeMillis();
        System.out.println("Program runtime:"+(endTime-startTime)+"ms");
        // Get the value of num and print it
        System.out.println("num.get() = "+num.get()); }}Copy the code

The results normally reach 20000 and the efficiency is not low

Num.get () = 20000

Let’s take a look at synchronized and lock

Program running time: 46ms end,synchronized method snUM = 20000

Program running time: 47ms end, LOCK method lnum = 20000

Now all three seem to be working pretty well

When we bring up the number of cycles

        for (int i = 0; i < 20000; i++) {
            new Thread(()->{
                for (int j = 0; j < 10000; j++) {
                    //do something
                }
            }).start();
        }
Copy the code

Program running time: 2136ms end,synchronized method snUM = 200000000

Program running time: 6287ms end, LOCK method lnum = 200000000

Num.get () = 200000000

Synchronized is dominant, atomic is second, and lock is the longest

3. Forbid instruction rearrangement

What is order reordering?

The program we write, the computer doesn’t do what you write

For performance reasons, the compiler and CPU may reorder instructions

What is instruction reordering: prioritizing certain instructions to improve efficiency without affecting results

Source code –> compiler optimizations –> instruction parallelism may also be rearranged –> memory systems –> execution

The as – if – serial semantics

The result of a single-threaded program cannot be changed, no matter how it is reordered. Okay

The compiler, runtime, and processor must comply with the AS-IF-Serial semantics

The processor considers dependencies between data when reordering instructions.

int x = 1; / / 1
int y = 2; / / 2
x = x + 5; / / 3
y = x * x; / / 4What we expect:1234But it may be executed back into2134 1324It can't be4123! Because y assignment depends on x!Copy the code

The instruction reordering can be avoided simply by adding volatile

Memory barriers are added to both reads and writes of memory areas: sequential exchange of instructions at rest

Function:

  • Ensure the order in which certain operations are executed!
  • Memory visibility of certain variables is guaranteed (visibility is achieved with these features, volatile)

Volatile can maintain visibility. Atomicity is not guaranteed. Thanks to the memory barrier, instruction reordering is guaranteed to be avoided!

The singleton pattern

For singleton patterns, check out my notes on singleton patterns

Understand the CAS

What is CAS?

CAS, the abbreviation of compare and swap, Chinese translation into compare and exchange

CAS is a concurrent primitive for the CPU and an atomic operation at the operating system level

We looked at an Unsafe class in the AtomicInteger class

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
Copy the code

As we know, Java can not operate the system directly, but through the keyword native C++ to operate the bottom layer of the system

The Unsafe class is Java’s “back door,” directly operating system memory

Before I talk about CAS, I want to look at the compareAndSet method, swap and assign

private volatile static AtomicInteger num=new AtomicInteger(0);
num.compareAndSet(1.2);
Copy the code

When num is 1, replace it with 2. CAS is similar to this

Let’s see how AtomicInteger increases atomicity in the previous section

    private volatile static AtomicInteger num=new AtomicInteger(0);

    public static void add(a){
        // increment and return
        num.getAndIncrement();
    }
Copy the code

Then look at the getAndIncrement method

You can see the getAndAddInt method of the Unsafe class being called

	private static final Unsafe unsafe = Unsafe.getUnsafe();    
	public final int getAndIncrement(a) {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
Copy the code

Looking at the source code for the broadening class’s getAndAddInt method

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
Copy the code

That is, we call this (AtomicInteger), valueOffset (memory address offset), and the value I that needs to be added

That’s basically what we did in the last section

Get static num and put it at the top of the stack

Put the constant 1 at the top of the stack

Add two values at the top of the current stack and place the result at the top of the stack (num=num+1)

The result at the top of the stack is assigned to static num

A simple explanation of the sample CAS source code

The value of the current element, var5, can be retrieved from the var1 instance object and its memory address, var2

It then loops through the CAS operation compareAndSwapInt to compare whether the var5 value has not changed and, if it is, to the new value

We also call this spin operation, or spin lock

Compares values in current working memory with values in main memory

If the value is expected, then the operation is performed!

If not, keep cycling!

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
Copy the code

If according tovar1andvar2The value that I took out, and the value that I took outvar5Same, then I willvar5Replace withvar5 + var4And return

Method call native CAS primitive (var4 = 1)

The disadvantage of the CAS

  • The loop takes time
  • Only one shared variable is guaranteed atomicity at a time
  • ABA problem

What are ABA problems?

Unbeknownst to the other threads, there comes a new leopard cat, but the other threads do not know that this has been replaced, nor do they know whether this leopard cat is still the original leopard cat

Let’s say A is equal to 1

Then thread B calls cas(1,3) and cas(3,1).

I’m going to replace the 1 with the 3, and then I’m going to replace the 3 with the 1

For thread A, the value of A is still 1, but it may not be the same 1

It doesn’t matter much for the base type because it points to the constant pool

If it is a reference type, there may be a problem. The value passed may not change, but the object does!

Solutions to ABA problems

Atomic operations with version numbers, see the next section, “Atomic References.”

Atomic reference

Optimistic locking can be implemented not only with CAS operations, but also with a version number mechanism

We use the AtomicStampedReference class to implement the version number mechanism

Note that the value of the compareAndSet method is equal to the value of the compareAndSet method. Therefore, the value of the Integer can only be used between -128 and 127!

    static final int low = -128;
    static final int high;
	assert IntegerCache.high >= 127;

	public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
Copy the code

Code implementation

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

/ * * *@Author if
 * @Description: Version number mechanism * Note that if Integer is used, only the range from -127 to 128 * is used because !!!!! is used at the bottom * expectedReference == current.reference * *@Date 2021-11-09 下午 04:33
 */
public class CASDemo {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1.1);

        new Thread(()->{
            // Get the version number
            int stamp = atomicInteger.getStamp();
            System.out.println("The original a-stamp =" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("A -> "+atomicInteger.compareAndSet(1.2, stamp, (stamp + 1)));
            System.out.println("A-stamp =" + atomicInteger.getStamp());
            System.out.println("= = = = = = = = = = = = = = = = = = = = = = =");
        },"A").start();

        new Thread(()->{
            int stamp = atomicInteger.getStamp();
            System.out.println("B - stamp = " + stamp);
            System.out.println("B -> "+atomicInteger.compareAndSet(1.2, stamp, stamp + 1));
            System.out.println("B - stamp =" + atomicInteger.getStamp());
            System.out.println("= = = = = = = = = = = = = = = = = = = = = = =");
        },"B").start();


        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("GetStamp ="+atomicInteger.getStamp());
        System.out.println("AtomicInteger value ="+atomicInteger.get(new int[]{atomicInteger.getStamp()})); }}Copy the code

The understanding of various locks

1. Fair locks and unfair locks

We should have seen this when we studied the Lock class

Lock lock=new ReentrantLock();

Let’s take a look at the constructor for ReentrantLock

   // Create an unfair lock Nonfair by default
	public ReentrantLock(a) {
        sync = new NonfairSync();
    }
	// Boolean creates a Fair lock if the value is true, or Nonfair if the value is not true
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
Copy the code

Fair Lock: Fair: First come first served Non-fair Lock: Unfair: Can cut in line (default)

2. Reentrant lock

Reentrant: a thread that has acquired a lock can acquire it again without deadlock

Synchronized and ReentrantLock are reentrant

  • Implicit locks (that is, those used by the synchronized keyword) are reentrant by default
  • Explicit locks (that is, locks) also have reentrantlocks

One of the points of reentrant locks is to prevent deadlocks

Of course, a lock() must also be unlocked () as many times as it can be unlocked

This is done by associating each lock with a request counter and a thread that owns it

When the count is 0, the lock is considered unused, and when a thread requests an unused lock, the JVM records the owner of the lock and sets the request counter to 1

If the same thread requests the lock again, the counter is incremented

The counter is decremented each time the occupying thread exits the synchronized block. Until the counter reaches 0, the lock is released

The default locks for the current phase are reentrant locks (also known as recursive locks)

If you want to achieve non-reentrant effects, you can set up your own class that inherits Lock

A member variable is bound to a thread. The first call assigns the current thread to the bound thread, and subsequent calls to lock determine whether the bound thread is the current thread. If the current thread is the bound thread, wait

For details, see this blog at blog.csdn.net/wb_zjp28312…

3. Spin locks

In fact, when WE talked about CAS, we talked about spin manipulation

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
Copy the code

“Spin” can be understood as “self-rotation,” where “spin” refers to a “loop,” such as a while loop or a for loop

“Spin” is the self repeating the cycle until the goal is achieved

Unlike normal locks, which block if the lock is not acquired

The main difference between a non-spin lock and a spin lock is that if it encounters a lock failure, it blocks the thread until it is woken up. And the spin lock will keep trying

The advantage of a spinlock is that it keeps the thread in a Runnable state by constantly trying to acquire the lock in a loop, which saves the overhead of thread state switching

However, if the critical area is large and the thread takes a long time to release the lock, then spin-locking is not suitable because the spin will always use up the CPU but can’t get the lock, wasting resources

4, a deadlock

What is a deadlock

Deadlock: In multithreading, a blockage caused by competing for resources or by communicating with each other that cannot proceed without external force

The system is said to be in a deadlock state or a deadlock occurs in the system. The processes that are always waiting for each other are called deadlocked processes

Deadlock conditions occur

  1. Must be two or more processes (threads)
  2. There must be competing resources

A picture with you understand deadlock!

Code sample

import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @Description: Deadlock example *@Date 2021-11-09 下午 06:54
 */
public class DeadLockDemo {
    public static void main(String[] args) {
        Object lockA=new Object();
        Object lockB=new Object();

        new Thread(()->{
            synchronized (lockA){
                System.out.println(Thread.currentThread().getName()+"Get lock A");

                try{
                    TimeUnit.SECONDS.sleep(1);
                }catch(Exception e){
                    e.printStackTrace();
                }

                synchronized (lockB){
                    System.out.println(Thread.currentThread().getName()+"Acquired lock B"); }}},"A").start();

        new Thread(()->{
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"Acquired lock B");

                try{
                    TimeUnit.SECONDS.sleep(1);
                }catch(Exception e){
                    e.printStackTrace();
                }

                synchronized (lockA){
                    System.out.println(Thread.currentThread().getName()+"Get lock A"); }}},"B").start(); }}Copy the code

The code should be clear, the middle sleep is for fear of a thread snatching two locks at the same time will not succeed

A grabs the lock of A and goes to sleep, B grabs the lock of B and goes to sleep, and THEN A wakes up and tries to get the lock of B, but of course he can’t get it. B also tries to get the lock of A, but of course he can’t get it either. All the locks needed are in the hands of the other party, so he naturally falls into deadlock

User A has obtained lock A. User B has obtained lock B

How to check for deadlocks?

  • Locating process NUMBER:
    • In a Windows command window, run thejps -lView the PID of the current Java process, through the package path is easy to distinguish their own program process
  • Find thread status and problem code:
    • View pid, enterjstack -l 15528, 15528 is the process PID
# check the process
>jps -l
14720
1464 org.jetbrains.jps.cmdline.Launcher
15528 com.ifyyf.test.deadlock.DeadLockDemo
4040 org.jetbrains.idea.maven.server.RemoteMavenServer36
9176 sun.tools.jps.Jps

# Check for specific error messages (too long, arbitrary)
>jstack -l 15528

Found one Java-level deadlock:
=============================
"B":
  waiting to lock monitor 0x0000000002e39fe8 (object 0x000000076b614298, a java.lang.Object),
  which is held by "A"
"A":
  waiting to lock monitor 0x0000000002e3c928 (object 0x000000076b6142a8, a java.lang.Object),
  which is held by "B"

Java stack information for the threads listed above:
===================================================
"B":
        at com.ifyyf.test.deadlock.DeadLockDemo.lambda$mainThe $1(DeadLockDemo.java:42)
        - waiting to lock <0x000000076b614298> (a java.lang.Object)
        - locked <0x000000076b6142a8> (a java.lang.Object)
        at com.ifyyf.test.deadlock.DeadLockDemo$$Lambda$2/1078694789.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"A":
        at com.ifyyf.test.deadlock.DeadLockDemo.lambda$main$0(DeadLockDemo.java:26)
        - waiting to lock <0x000000076b6142a8> (a java.lang.Object)
        - locked <0x000000076b614298> (a java.lang.Object)
        at com.ifyyf.test.deadlock.DeadLockDemo$$LambdaThe $1/990368553.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.
Copy the code

End and spend

This JUC study is not particularly in-depth, it can be said that it is a simple door

This code is put in my Gitee warehouse, you can help yourself