6W + word, thoroughly understand JUC!

Keywords: Java

Source: blog.csdn.net/wangwenpeng0529/article/details/105769978

brief introduction

Java. Util. Concurrent (JUC) package is provided in Java 5.0. In this package, tool classes commonly used in concurrent programming are added to define custom subsystems similar to threads, including thread pool, asynchronous IO and lightweight task framework; Collection implementations designed for multithreading contexts are also provided

volatile keyword

Memory visibility

Memory Visibility means that when one thread is using the object state and another thread is modifying the state at the same time, it is necessary to ensure that other threads can see the state changes after one thread modifies the object state.

Visibility error means that when read and write operations are executed in different threads, we cannot ensure that the thread executing the read operation can see the values written by other threads in time, sometimes even impossible.

We can ensure that objects are published safely through synchronization. In addition, we can also use a more lightweight volatile variable.

Java provides a slightly weaker synchronization mechanism, the volatile variable, to ensure that the update operation of the variable is notified to other threads. Volatile can be regarded as a lightweight lock, but it is different from lock:

  • For multithreading, it is not a mutually exclusive relationship
  • Atomic operation of variable state cannot be guaranteed

Problem code example

/**
 * @ClassName TestVolatile
 * @Description: Thread The flag has been modified, but the main thread still gets false
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            if (td.isFlag()) {
                System.out.println("______________");
                break;
            }
        }
    }
}

class ThreadDemo implements Runnable {
    private boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            //Increase the probability of this problem
            Thread.sleep(200);
        } catch (Exception e) {
        }
        flag = true;
        System.out.println("flag=" + isFlag());
    }
}

Two threads modify this flag at the same time. Why does main get the value before this modification

Memory analysis

Solution, lock

public class TestVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            synchronized (td) {
                if (td.isFlag()) {
                    System.out.println("______________");
                    break;
                }
            }
        }
    }
}

With a lock, the while loop can read data from the main memory every time, so that it can read true. However, once a lock is added, only one thread can access it at a time. When one thread holds a lock, the others will block, which is very inefficient. If you don't want to lock and want to solve the problem of memory visibility, you can use the volatile keyword.

volatile

private volatile boolean flag = false;

volatile keyword: when multiple threads operate to share data, it can ensure that the data in memory is visible. Compared with synchronized, it is a lighter synchronization strategy.

be careful:

  • volatile is not "mutually exclusive"
  • volatile cannot guarantee the "atomicity" of variables

Atomicity

Atomicity means that operations cannot be subdivided

Problem code

package com.atguigu.juc;

/**
 * @ClassName TestAtomicDemo
 * @Description:
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {

    private int serialNumber = 0;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
    }

    public int getSerialNumber() {
        return serialNumber++;
    }
}

It looks like the memory visibility problem above. Is it OK to add the volatile keyword? In fact, it is not, because adding volatile is just equivalent to that all threads operate data in main memory, but it is not mutually exclusive. For example, two threads read 0 in main memory at the same time, and then increase it at the same time, and write it to main memory at the same time. The result is not good Yes, duplicate data will appear.

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName TestAtomicDemo
 *
 * Atomic variables: some atomic variables are provided under the java.util.concurrent.atomic package.
 *  1. volatile Ensure memory visibility
 *  2. CAS(Compare-And-Swap) The algorithm ensures the atomicity of data variables
 *   CAS The algorithm is the hardware support for concurrent operations
 *   CAS Contains three operands:
 *   ①Memory value V
 *   ②Estimated value A
 *   ③Update value B
 *   If and only if V == A, V = B; otherwise, no action will be performed.
 *
 * @Description:
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {

    private AtomicInteger serialNumber = new AtomicInteger(0);

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
    }

    public int getSerialNumber() {
        return serialNumber.getAndIncrement();
    }
}

AtomicInteger is an atomic integer. After replacing it, it is found that it can ensure thread safety

Connected to the target VM, address: '127.0.0.1:61323', transport: 'socket'
Thread-4:1
Thread-6:4
Thread-0:3
Thread-7:9
Thread-2:2
Thread-5:6
Thread-3:5
Thread-1:0
Thread-9:7
Thread-8:8
Disconnected from the target VM, address: '127.0.0.1:61323', transport: 'socket'

CAS algorithm

The atomicity problem and memory visibility problem are solved

CAS (compare and swap) is a hardware support for concurrency. It is a special instruction in the processor designed for multiprocessor operation, which is used to manage concurrent access to shared data. CAS is an implementation of lockless non blocking algorithm.

CAS contains three operands:

  • Memory value to be read and written V value to be compared A new value to be written B
  • If and only if the value of V is equal to A, CAS updates the value of V by atomic means with the new value B, otherwise no operation will be performed.
  • When CAS comparison fails, the CPU will not be abandoned, and it will be executed repeatedly until you modify the data in main memory

Simulated CAS algorithm

/**
 * @ClassName TestCompareAndSwap
 * @Description: cas Analog with lock, the bottom layer is not synchronized
 * cas Before each modification, the get comparison operation is performed
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestCompareAndSwap {

    public static void main(String[] args) {
        final CompareAndSwap cas = new CompareAndSwap();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int expectValue = cas.getValue();
                    System.out.println(cas.compareAndSet(expectValue, (int) Math.random() * 101));
                }
            }).start();
        }
    }
}

class CompareAndSwap {
    public int value;

    //Get memory value
    public synchronized int getValue() {
        return value;
    }

    //Compare and exchange
    public synchronized int compareAndSwap(int expectValue, int newV) {
        int oldV = value;
        //If the memory value is consistent with the estimated value, replace it
        if (oldV == expectValue) {
            this.value = newV;
        }
        return oldV;
    }

    //Set call comparison and exchange to see whether the expected value is consistent with the original value
    public synchronized boolean compareAndSet(int expectValue, int newV) {
        return expectValue == compareAndSwap(expectValue, newV);
    }
}

Recommend a basic Spring Boot tutorial and practical example: https://github.com/javastacks/javastack

Atomic variable

A gadget package that supports thread safe programming to unlock a single variable. In fact, the classes in this package can extend the concepts of volatile values, fields, and array elements to those that also provide atomic condition update operations.

Instances of the classes AtomicBoolean, AtomicInteger, AtomicLong, and AtomicReference each provide access to and updates to individual variables of the corresponding type. Each class also provides appropriate utility methods for the type.

The AtomicIntegerArray, AtomicLongArray, and AtomicReferenceArray classes further extend atomic operations and provide support for these types of arrays. These classes also stand out in providing volatile access semantics for their array elements, which is not supported for ordinary arrays.

Core method: boolean compareAndSet(expectedValue, updateValue)

The java.util.concurrent.atomic package provides some common classes for atomic operations:

AtomicBoolean ,
AtomicInteger ,
AtomicLong ,
AtomicReference
AtomicIntegerArray ,
AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
AtomicStampedReference3-ConcurrentHashMap

Lock segmentation mechanism ConcurrentHashMap

Each segment of the thread safe hash table is an independent lock

Java 5.0 provides a variety of concurrent container classes in the java.util.concurrent package to improve the performance of synchronization containers.

The ConcurrentHashMap synchronization container class is a thread safe hash table added by Java 5. The operation on multithreading is between HashMap and Hashtable. The internal "lock segmentation" mechanism is used to replace the exclusive lock of Hashtable, so as to improve performance.

This package also provides collection implementations designed for multithreading contexts: ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet, CopyOnWriteArrayList, and CopyOnWriteArraySet. When many threads are expected to access a given collection, ConcurrentHashMap is usually better than synchronized HashMap, and ConcurrentSkipListMap is usually better than synchronized tree AP. CopyOnWriteArrayList is better than synchronized ArrayList when the expected reading and traversal are much greater than the number of updates to the list.

Concurrent HashMap is a thread safe Hash Table. We know that HashMap is thread unsafe, and Hash Table is thread safe with lock, so its efficiency is low. HashTable locking is to lock the entire Hash Table. When multiple threads access it, only one thread can access it at the same time. Parallel becomes serial, so the efficiency is low. Therefore, concurrent HashMap is provided after JDK1.5, which adopts the lock segmentation mechanism.

After 1.8, the bottom layer was replaced by CAS and the lock segmentation mechanism was abandoned. CAS has basically reached the realm of no lock.

In addition, all the interview questions and answers of Java 8 + series have been sorted out. Wechat searches the Java technology stack and sends them in the background: the interview can be read online.

CopyOnWrite writes and copies

package com.atguigu.juc;import java.util.*;import java.util.concurrent.CopyOnWriteArrayList;/* * CopyOnWriteArrayList/CopyOnWriteArraySet : "Write and copy "* Note: when adding more operations, the efficiency is low, because each addition will be copied, which is very expensive. Multiple concurrent iterative operations can be selected. */public class TestCopyOnWriteArrayList {    public static void main(String[] args) {        HelloThread ht = new HelloThread();        for (int i = 0; i < 10; i++) {            new Thread(ht).start();        }    }}class HelloThread implements Runnable {    //private static List<String> list = Collections.synchronizedList(new ArrayList<String>()); // Each modification will copy and add. If there are many operations, it is not suitable to select this private static copyonwritearraylist < string > List = new copyonwritearraylist < > (); Static {list. Add ("AA"); list. Add ("BB"); list. Add ("CC");} @ override public void run() {iterator < string > it = list. Iterator(); while (it. Hasnext()) {system. Out. Println (it. Next()); list. Add ("AA"); / / concurrent modification exception}}}

CountDownLatch lockout

Locking. When some operations are completed, the current operation will continue only after the operations of all other threads are completed. Java 5.0 provides a variety of concurrent container classes in the java.util.concurrent package to improve the performance of the synchronization container.

CountDownLatch is a synchronization helper class that allows one or more threads to wait until a set of operations are completed in other threads.

Locking can delay the progress of a thread until it reaches the termination state. Locking can be used to ensure that some activities do not continue until other activities are completed:

  • Ensure that a calculation continues to execute after all the resources it needs are initialized;
  • Ensure that a service is started only after all other services it depends on have been started;
  • Wait until all participants of an operation are ready to continue.
import java.util.concurrent.CountDownLatch;/** * @ClassName TestCountDownLatch * @Description: The current thread can only continue to execute after the locking operation is completed by other threads * @ Author: WangWenpeng * @Version 1.0 */public class TestCountDownLatch {    public static void main(String[] args) throws InterruptedException {        final CountDownLatch latch = new CountDownLatch(5);        LatchDemo ld = new LatchDemo(latch);        //Calculate execution time long start = system. Currenttimemillis(); For (int i = 0; I < 5; I + +) {new thread (LD). Start();} / / lock and wait for other threads to execute latch. Await(); long end = System.currentTimeMillis();         System.out.println ("execution time ============================================================================= class LatchDemo implements Runnable {    private CountDownLatch latch;    public LatchDemo(CountDownLatch latch) {        this.latch = latch;    }    @Override    public void run() {        synchronized (this) {            try {                for (int i = 0; i < 1000; i++) {                    if (i % 2 == 0)  {system. Out. Println (thread. Currentthread(). Getname() + "--------------" + I);}}} finally {/ / after the thread is executed, countdown minus one latch.countDown();}}}}

Implement Callable interface

Java 5.0 provides a new way to create execution threads in java.util.concurrent: Callable interface

The Callable interface is similar to Runnable. Both are designed for classes whose instances may be executed by another thread. However, Runnable does not return results and cannot throw checked exceptions.

Callable needs to rely on FutureTask, which can also be used as locking.

package com.atguigu.juc;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** * @ClassName TestCallable * @Description: * @Author: WangWenpeng * @Version 1.0 */public class TestCallable {    public static void main(String[] args) throws ExecutionException, InterruptedException {        CallableThreadDemo td = new CallableThreadDemo();        //futureTask implementation class supports receiving operation results. futureTask < integer > result = new futureTask < > (TD); new thread (result). Start(); / / the thread starts running Integer sum = result.get(); / / the result can only be obtained after the thread completes execution. It can also be used for locking operation as a waiting item System.out.println("sum" + sum);} }/*** @ description has one more return value of the method and can throw an exception * @ author wangwenpeng * @ param * / class callablethreaddemo implements callable < integer > {@ override public integer call() throws exception {int sum = 0; for (int i = 0; I < 100; I + +) {sum + = I;} return sum;}} //class ThreadDemo implements Runnable{//    @Override//    public void run() {    }//}

Sync Lock display Lock

Before Java 5.0, the only mechanisms that can be used to coordinate access to shared objects were synchronized and volatile. Some new mechanisms have been added after Java 5.0, but it is not a method to replace the built-in lock, but as an optional advanced function when the built-in lock is not applicable.

ReentrantLock implements the Lock interface and provides the same mutex and memory visibility as synchronized. However, compared with synchronized, it provides higher flexibility in handling locks.

package com.atguigu.juc;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @ClassName TestLock * @Description: More flexible way of synchronization lock * lock lock unlock lock * @ Author: WangWenpeng * @Version 1.0 */public class TestLock {    public static void main(String[] args) {        Ticket ticket = new Ticket();        new Thread(ticket, "1 Number window").start();        new Thread(ticket, "2 Number window").start();        new Thread(ticket, "3 Number window").start();    }}class Ticket implements Runnable {    private int ticket = 100;    private Lock lock = new ReentrantLock();    @Override    public void run() {        //In this way, there is no problem in buying tickets / / while (ticket > 0) {/ / system.out.println (thread. Currentthread(). Getname() + "ticket sales are completed, and the remaining tickets are" + -- ticket); / /} / / a negative ticket number appears in the record of amplification problems / / while (true) {/ / if (ticket > 0) {/ / try {/ / thread.sleep (200) ; / / system. Out. Println (thread. Currentthread(). Getname() + "ticket sales are completed, and the remaining tickets are" + -- ticket); / /} catch (interruptedexception E) {/ / e.printstacktrace(); / /} / / lock and release locks explicitly while (true) {lock. Lock() ; try {if (ticket > 0) {try {thread. Sleep (200); system. Out. Println (thread. Currentthread(). Getname() + "ticket sales completed, remaining tickets are" + -- ticket);} catch (interruptedexception E) {e.printstacktrace() ;                    }                }            } finally {                lock.unlock();            }        }    }}

Wait wake mechanism of lock

/**
 * @ClassName TestProductorAndConsumer
 * @Description: Producer consumer model
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestProductorAndConsumer {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);

        //When there is no waiting wake-up mechanism
        //Producers have been producing without testing consumers, which may cause data loss
        //Consumers have been spending without considering that producers may cause repeated consumption
        new Thread(productor, "producer a").start();
        new Thread(consumer, "consumer a").start();
    }
}

/**
 * clerk
 */
class Clerk {
    //There is a security problem with inventory sharing data
    private int product = 0;

    //Purchase
    public synchronized void get() {
        if (product >= 10) {
            System.out.println("The product is full and cannot be added");
            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        } else {
            this.notifyAll();
            System.out.println(Thread.currentThread().getName() + "The clerk purchases 1 product, and the inventory is" + ++product);
        }
    }

    //Sell goods
    public synchronized void sale() {
        if (product <= 0) {
            System.out.println("The product is out of stock and cannot be sold");
            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        } else {
            System.out.println(Thread.currentThread().getName() + "The clerk sells 1 product with an inventory of" + --product);
            this.notifyAll();
        }
    }
}

/**
 * producer
 */
class Productor implements Runnable {
    private Clerk clerk;
    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.get();
        }
    }
}

/**
 * @Description consumer
 * @Author WangWenpeng
 * @Date 6:45 2020/4/27
 * @Param
 */
class Consumer implements Runnable {
    private Clerk clerk;
    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }
    }
}

lock problem

The producer waits to increase the probability of problems. The inventory vacancy is changed to 1

 if (product >= 1) {
            System.out.println("The product is full and cannot be added");
--------------------------------------------------------------------------------------

@Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
            clerk.get();
        }
    }

When the consumer is equal to 0, two consumers produce at the same time, and then stop. No other thread wakes up, resulting in stopping at the producer.

The solution is to remove else so that he can walk the wake-up method.

//Purchase public synchronized void get() {if (Product > = 1) {system. Out. Println ("the product is full and cannot be added"); try {this. Wait();} catch (interruptedexception E) {}} this. Notifyall(); system. Out. Println (thread. Currentthread(). Getname() + "the inventory of one product purchased by the clerk is" + + + product);} //Selling goods public synchronized void sale() {if (product < = 0) {system. Out. Println ("product is out of stock and cannot be sold"); try {this. Wait();} catch (interruptedexception E) {}} system.out.println (thread. Currentthread(). Getname() + "the clerk sells 1 product, the inventory is" + -- Product); this. Notifyall();}

Let the thread go to notifyall to avoid stopping at the producer.

spurious wakeup

After adding two consumers and two producers, if there is no inventory now, both consumers stop wait ing. Then the producer increases the inventory by one to awaken all consumers. At this time, two consumers consume one inventory at the same time, resulting in a negative inventory. This is false awakening.

In the wait method of object class, false wake-up is possible, so this wait method should always be used in the loop

resolvent

Replace the if in the code with a while loop

 //Purchase public synchronized void get() {while (Product > = 1) {/ / wait is used in the loop System.out.println("the product is full and cannot be added"); try {this. Wait();} catch (interruptedexception E) {}} this. Notifyall(); system. Out. Println (thread. Currentthread()) . getname() + "the clerk purchases 1 product, and the inventory is" + + + product);} / / public synchronized void sale() {while (product < = 0) {System.out.println("the product is out of stock and cannot be sold"); try {this. Wait();} catch (interruptedexception e) {}} system.out.println (thread. Currentthread(). Getname() + "the clerk sells one product, and the inventory is" + -- Product); this. Notifyall();}

Control thread communication Condition

The Condition interface describes Condition variables that may be associated with locks. These variables are similar in usage to implicit monitors accessed using Object.wait, but provide more powerful functions. It should be noted that a single Lock may be associated with multiple Condition objects. To avoid compatibility problems, the name of the Condition method is the same as that in the corresponding Object version Different.

In the Condition object, the wait, notify and notifyAll methods correspond to await, signal and signalAll respectively. The Condition instance is essentially bound to a Lock. To obtain a Condition instance for a specific Lock instance, use its newCondition() method.

/** * clerk */class ClerkLock {    //There is a security problem with the inventory shared data. private int product = 0; / / use lock to remove synchronized this.wait and lock, which are two locks. Use lock to unify private lock = new reentrantlock(); private condition = lock. Newcondition(); / / public void get() {lock. Lock(); try {while (Product > = 1) {system. Out. Println ("the product is full and cannot be added"); try {condition. Await();} catch (interruptedexception E) {}} condition. Signalall(); system. Out. Println (thread. Currentthread(). Getname() + "the clerk purchases 1 product, and the inventory is" ++ + product);} finally {lock. Unlock();}} / / public synchronized void sale() {lock. Lock(); try {while (product < = 0) {system.out.println ("product is out of stock and cannot be sold"); try {condition. Await();} catch (interruptedexception E) {}} system.out.println (thread. Currentthread(). Getname() + "the clerk sells one product, and the inventory is" + -- Product); condition. Signalall();} finally {}}}

Here, the clerk's code is all processed as condition, and his method is used to realize thread communication.

Sequential alternation of threads sequential alternation of threads

Write A program and start three threads. The IDs of these three threads are A, B and C respectively. Each thread prints its own ID on the screen 10 times. It is required that the output results must be displayed in order.

For example: abcabcabcabc...... recurse 9-ReadWriteLock read-write lock read-write lock ReadWriteLock in sequence

package com.atguigu.juc;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @ClassName TestABCAlternate * @Description: Thread alternate printing * @ Author: WangWenpeng * @Version 1.0 */public class TestABCAlternate {    public static void main(String[] args) {        Alternate alternate = new Alternate();        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0; i <= 20; i++) {                    alternate.loopA(i);                }            }        }, "A").start();        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0; i <= 20; i++) {                    alternate.loopB(i);                }            }        }, "B").start();        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0; i <= 20; i++) {                    alternate.loopC(i);                }            }        }, "C").start();    }}class Alternate {    private int number = 1;//Thread number currently executing private lock lock = new reentrantlock(); private Condition condition1 = lock.newCondition();     private Condition condition2 = lock.newCondition();     private Condition condition3 = lock.newCondition();     Public void loopa (int totalloop) {lock. Lock(); try {/ / 1. Judge thread 1 if (number! = 1) {condition1. Await();} / / 2. Start printing for (int i = 0; I < 5; I + +) {system.out.println (thread. Currentthread(). Getname() + "\ T" + I + "\ T" + totalloop) If} / / 3. Wake up thread 2 number = 2; condition2. Signal();} catch (interruptedexception E) {e.printstacktrace();} finally {lock. Unlock();}} public void loopb (int totalloop) {lock. Lock(); try {/ / 1. Judge thread 1 (number! = 2) {condition2. Await();} / / 2. Start printing for (int i = 0; I < 5; I + +) {system.out.println (thread. Currentthread(). Getname() + "\ T" + I + "\ T" + totalloop);} / / 3. Wake up thread 2 number = 3; condition3. Signal();} catch (interruptedexception E) {e.printstacktrace();} finally {lock. Unlock();}} public void loopc (int totalloop) {lock. Lock(); try {/ / 1. Judge thread 1 if (number! = 3) {condition3. Await();} / / 2. Start printing for (int i = 0; I < 5; I + +) {system.out.println (thread. Currentthread(). Getname() + "\ T" + I + "\ T" + totalloop);} / / 3. Wake up thread 2 number = 1; condition1. Signal();} catch (interruptedexception E) {e.printstacktrace();} finally {lock. Unlock();}     }}

ReadWriteLock read write lock

ReadWriteLock maintains a pair of related locks, one for read-only operation and the other for write operation. As long as there is no writer, the read lock can be held by multiple reader threads at the same time. The write lock is exclusive.

The ReadWriteLock read operation usually does not change the shared resources, but when performing the write operation, the lock must be obtained exclusively. For the data structure with the majority of read operations, ReadWriteLock can provide higher concurrency than the exclusive lock. For the read-only data structure, the invariance contained in it does not need to consider the lock operation at all.

Read lock is that multiple threads can work together, and write lock is exclusive

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName ReadWriteLock
 * @Description: Read write lock read and write are not mutually exclusive write and write are mutually exclusive
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriteLockDemo demo = new ReadWriteLockDemo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.set((int) (Math.random() * 101));
            }
        }, "writeLock").start();

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.get();
                }
            }, "readLock-" + i).start();
        }
    }
}

class ReadWriteLockDemo {
    private int number = 0;
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    //read
    public void get() {
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "read:" + number);
        } finally {
            lock.readLock().unlock();
        }
    }

    //write
    public void set(int number) {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "write:" + number);
            this.number = number;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Thread eight lock

  • If there are multiple synchronized methods in an object, as long as one thread calls one of the synchronized methods at a certain time, other threads can only wait. In other words, only one thread can access these synchronized methods at a certain time
  • The lock is the current object this. After being locked, other threads cannot enter other synchronized methods of the current object
  • 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.
  • After changing to static synchronization method, the situation changes again
  • All non-static synchronization methods use the same lock - the instance object itself, that is, if the non-static synchronization method of an instance object obtains the lock, other non-static synchronization methods of the instance object must wait for the method that obtains the lock to release the lock, but the non-static synchronization methods of other instance objects are the same as the non-static synchronization methods of the instance object The step method uses different locks, so they can acquire their own locks without waiting for the non static synchronous method of the instance object that has acquired the lock to release the lock.
  • All static synchronization methods also use the same lock - class object itself. These two locks are two different objects, so there will be no race conditions between static synchronization methods and non static synchronization methods. However, once a static synchronization method obtains a lock, other static synchronization methods must wait for the method to release the lock before obtaining the lock, regardless of whether it is the same Between static synchronization methods of instance objects or between static synchronization methods of different instance objects, as long as they are instance objects of the same class!
/**
 * 1. Two common synchronization methods, two threads, standard printing, printing? / / one two
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
    number.getTwo();
            }
        }).start();
    }
}
class Number {
    public synchronized void getOne() {
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
}
/**
2. Add Thread.sleep() to getOne(), print? / / one two
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
    number.getTwo();
            }
        }).start();
    }
}
class Number {
    public synchronized void getOne() {
        try {
            Thread.sleep(3000);//Let one sleep for three seconds
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
}
/*
 *3. Add a common method getThree() to print? / / three one two
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
    number.getTwo();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
    number.getThree();
            }
        }).start();
    }
}
class Number {
    public synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
    //Common method
    public void getThree(){
     System.out.println("three");
    }
}
/*
 * 4. Two common synchronization methods, two Number objects, print? / / two one
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number2.getTwo();
            }
        }).start();
    }
}

class Number {
    public synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
}
/*
 * 5. Modify getOne() to a static synchronization method and print? / / two one
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }//In this way, you can't access static through an instance of a class. To demonstrate this problem
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getTwo();
            }
        }).start();
    }
}
class Number {
    //Static synchronization method
    public static synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
}
 /*
  * 6. Modify the two methods to be static synchronization methods, one Number object? / / one two
 */
public class TestThread8Monitor {

    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }//In this way, you can't access static through an instance of a class. To demonstrate this problem
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getTwo();
            }
        }).start();
    }
}
class Number {
    public static synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public static synchronized void getTwo() {
        System.out.println("two");
    }
}
 /*
 * 7. One static synchronization method, one non static synchronization method, two Number objects? / / two one
 */
public class TestThread8Monitor {

    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }//In this way, you can't access static through an instance of a class. To demonstrate this problem
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number2.getTwo();
            }
        }).start();
    }
}

class Number {
    public static synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public  synchronized void getTwo() {
        System.out.println("two");
    }
}
 /*
 * 8. Two static synchronization methods, two Number objects? / / one two
 */
public class TestThread8Monitor {

    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }//In this way, you can't access static through an instance of a class. To demonstrate this problem
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                number2.getTwo();
            }
        }).start();
    }
}
class Number {
    public static synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public static synchronized void getTwo() {
        System.out.println("two");
    }
}

The key of thread eight locks:

  • The lock of non static method is this by default, and the lock of static method is the corresponding Class instance
  • Only one thread can hold a lock at a time, regardless of several methods.

Thread pool

The fourth method to obtain threads: thread pool, an ExecutorService, which uses one of several possible pool threads to execute each submitted task. It is usually configured using the Executors factory method.

Thread pools can solve two different problems: because they reduce the overhead of each task call, they can usually provide enhanced performance when executing a large number of asynchronous tasks, and can also provide methods to bind and manage resources, including the threads used when executing task sets. Each ThreadPoolExecutor also maintains some basic statistics, such as the number of tasks completed.

To facilitate use across a large number of contexts, this class provides many adjustable parameters and extended hooks. However, it is strongly recommended that programmers use the more convenient Executors factory method:

  • Executors.newCachedThreadPool() (unbounded thread pool, allowing automatic thread recycling)
  • Executors.newFixedThreadPool(int) (fixed size thread pool)
  • Executors.newSingleThreadExecutor() (single background thread) they have predefined settings for most usage scenarios.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @Description 1, Thread pool: provides a thread queue in which all threads in waiting state are saved. It avoids the additional overhead of creation and destruction and improves the response speed.
 * 2, Architecture of thread pool:
 * java.util.concurrent.Executor : The root interface responsible for thread usage and scheduling
 *   |--**ExecutorService Sub interface: the main interface of thread pool
 *    |--ThreadPoolExecutor Implementation class of thread pool
 *    |--ScheduledExecutorService Sub interface: responsible for thread scheduling
 *     |--ScheduledThreadPoolExecutor : Inherit ThreadPoolExecutor and implement ScheduledExecutorService
 * 3, Tool class: Executors
 * ExecutorService newFixedThreadPool() : Create a fixed size thread pool
 * ExecutorService newCachedThreadPool() : Cache thread pool. The number of thread pools is not fixed and can be changed automatically according to demand.
 * ExecutorService newSingleThreadExecutor() : Create a single thread pool. There is only one thread in the thread pool
 * ScheduledExecutorService newScheduledThreadPool() : Create a fixed size thread that can delay or schedule the execution of tasks.
 * @Author WangWenpeng
 * @Param
 */
public class TestThreadPool {

    public static void main(String[] args) throws Exception {
        //1. Create thread pool
        ExecutorService pool = Executors.newFixedThreadPool(5);

        //submit Callable method
        List<Future<Integer>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<Integer> future = pool.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int sum = 0;
                    for (int i = 0; i <= 100; i++) {
                        sum += i;
                    }
                    return sum;
                }
            });
            list.add(future);
        }
        pool.shutdown();
        for (Future<Integer> future : list) {
            System.out.println(future.get());
        }

        //submit Runnable method
        ThreadPoolDemo tpd = new ThreadPoolDemo();
        //2. Assign tasks to threads in the thread pool
        for (int i = 0; i < 10; i++) {
            pool.submit(tpd);
        }
        //3. Close thread pool
        pool.shutdown();
    }
}

class ThreadPoolDemo implements Runnable {
    private int i = 0;

    @Override
    public void run() {
        while (i <= 100) {
            System.out.println(Thread.currentThread().getName() + " : " + i++);
        }
    }
}

Thread scheduling ScheduledExecutorService

An ExecutorService that schedules commands to run or execute periodically after a given delay.

public static void main(String[] args) throws Exception {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 5; i++) {
            Future<Integer> result = pool.schedule(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int num = new Random().nextInt(100);//Generate random number
                    System.out.println(Thread.currentThread().getName() + " : " + num);
                    return num;
                }
            }, 1, TimeUnit.SECONDS);
            System.out.println(result.get());
        }
        pool.shutdown();
    }

ForkJoinPool branch / merge framework

When necessary, fork a large task into several small tasks (when it cannot be disassembled), and then join and summarize the operation results of each small task.

The difference between JoinFork/Join framework and thread pool

Adopt "work stealing" mode:

  • When executing a new task, it can split it into smaller tasks, add the small task to the thread queue, and then steal one from the queue of a random thread and put it in its own queue.
  • Compared with the general implementation of thread pool, the advantage of fork/join framework is reflected in the processing mode of the tasks contained in it. In a general thread pool, if the task being executed by a thread cannot continue to run for some reason, the thread will be in a waiting state. In the fork/join framework implementation, if a subproblem cannot continue to run because it waits for the completion of another subproblem. Then the thread dealing with this sub problem will actively look for other sub problems that have not yet run to execute. This method reduces the waiting time of the thread and improves the performance.
public class TestForkJoinPool {
    public static void main(String[] args) {
        Instant start = Instant.now();
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 50000000000L);
        Long sum = pool.invoke(task);
        System.out.println(sum);
        Instant end = Instant.now();
        System.out.println("Time consuming:" + Duration.between(start, end).toMillis());//166-1996-10590
    }
}

class ForkJoinSumCalculate extends RecursiveTask<Long> {
    private static final long serialVersionUID = -259195479995561737L;

    private long start;
    private long end;

    private static final long THURSHOLD = 10000L;  //critical value

    public ForkJoinSumCalculate(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long length = end - start;
        if (length <= THURSHOLD) {
            long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long middle = (start + end) / 2;
            ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
            left.fork(); //Split and push into the thread queue at the same time
            ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle + 1, end);
            right.fork();
            return left.join() + right.join();
        }
    }
}

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2021 latest version)

2.Stop playing if/ else on the full screen. Try the strategy mode. It's really fragrant!!

3.what the fuck! What is the new syntax of xx ≠ null in Java?

4.Spring Boot 2.5 heavy release, dark mode is too explosive!

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Posted by txmedic03 on Tue, 26 Oct 2021 03:13:17 -0700