A brief talk about Juc concurrent programming — up

preface

This course will learn concurrent programming with JUC in Java with B station crazy God

The code for this course is on my personal Gitee repository

What is JUC?

  • java.util.concurrent juc
  • Java. Util. Concurrent. Atomic atomicity
  • Java. Util. Concurrent. The locks lock

Threads may be used in normal services

Or like the Runnable interface implementation, has no return value, and is less efficient than callable

java.util.concurrent Interface Callable

Processes and threads

We all know that the core of the computer is CPU, which undertakes all the computing tasks, while the operating system is the manager of the computer, which is responsible for task scheduling, resource allocation and management, and leads the entire computer hardware

  • Process is a dynamic execution process of a program with certain independent functions in a data set. It is an independent unit of the operating system for resource allocation and scheduling. It is the carrier of application program operation
  • Thread is a single sequence control flow in program execution. It is the smallest unit of program execution flow and the basic unit of processor scheduling and dispatching. A process can have one or more threads that share the memory space of the program (that is, the memory space of the process in which it is running)

Process: a program, qi.exe music.exe program collection; A process can often contain multiple threads, at least one!

How many threads does Java have by default? 2 main, GC threads: open a process Typora, write, autosave (thread responsible)

For Java: Thread, Runnable, Callable Can Java really start threads? Can’t open

// the local method calls the underlying C++, because Java runs on the JVM and cannot manipulate the hardware directly
private native void start0(a);
Copy the code

How many states does a thread have?

Thread.State, as you can see, is an enumeration

   public enum State {
        // The thread state of the threads that have not yet been started
       / / new
        NEW,
		/ / run
        RUNNABLE,
		/ / blocking
        BLOCKED,
		// Wait and die
        WAITING,
       	// Wait for timeout
        TIMED_WAITING,
		/ / termination
        TERMINATED;
    }
Copy the code

Concurrency and parallelism

  • Concurrency is when two or more events occur at the same time interval -> alternately
    • A core CPU, simulated out of multiple threads, fast alternate running
  • Parallelism is when two or more events occur at the same time -> simultaneously
    • Multi-core CPU, multiple threads executing simultaneously, thread pool

The goal of concurrent programming is to fully utilize every core of the CPU to achieve maximum processing performance

    // Get the number of CPU cores
    System.out.println(Runtime.getRuntime().availableProcessors());
Copy the code

Wait/sleep

1. From different classes

wait => Object

sleep => Thread

2. Release of locks

Wait releases the lock

Hold the lock to sleep, will not release!

3. The scope of use is different

Wait must be used in synchronized blocks of code

Sleep can be anywhere

4. Whether exceptions need to be caught (doubtful)

throws InterruptedException

Wait also needs to catch exceptions.

Sleep must catch exceptions

The Lock Lock

As long as it’s concurrent programming, you need locks!

Traditional synchronized locks

I’m not talking about thread pools here, but the general approach

Decouple the thread classes, eliminating the need to write a separate thread class that inherits the Runnable interface

Instead, use lambda expressions ()->{} to implement the Runnable interface to create threads

        new Thread(()->{
            //do something
        },"Name").start();
Copy the code

Then the synchronized lock method locks the object

        public synchronized void sale(a){
            if(num<=0)return;
            System.out.println(Thread.currentThread().getName()+"Buy the number"+(num--)+"One ticket, the rest."+num+"Ticket");
        }
Copy the code

It’s the same old story about selling tickets

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

        Ticket ticket = new Ticket();

        new Thread(()->{
            for(int i=0; i<100; i++){ ticket.sale();try {
                    Thread.sleep(10);
                } catch(InterruptedException e) { e.printStackTrace(); }}},"A").start();

        new Thread(()->{
            for(int i=0; i<100; i++){ ticket.sale();try {
                    Thread.sleep(10);
                } catch(InterruptedException e) { e.printStackTrace(); }}},"B").start();

        new Thread(()->{
            for(int i=0; i<100; i++){ ticket.sale();try {
                    Thread.sleep(10);
                } catch(InterruptedException e) { e.printStackTrace(); }}},"C").start();
    }
    static class Ticket{
        private int num=100;
        public synchronized void sale(a){
            if(num<=0)return;
            System.out.println(Thread.currentThread().getName()+"Buy the number"+(num--)+"One ticket, the rest."+num+"Ticket"); }}Copy the code

The Lock Lock

Java. Util. Concurrent. The locks. The Lock is a Lock of the interface

The recommended practice is to always immediately follow the call between the lock and the try block

The most common is locking before/after lock.lock(); And unlock the lock, unlock ()

It has several implementation classes:

  • ReentrantLock ReentrantLock
  • ReentrantReadWriteLock ReadLock read lock
  • ReentrantReadWriteLock. WriteLock write locks

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)

Use trilogy

  1. Lock lock=new ReentrantLock();Instantiate the lock object
  2. lock.lock();locked
  3. inIn the finallylock.unlock();unlock
    static class Ticket{
        private int num=100;
        // Reentrant lock
        Lock lock=new ReentrantLock();
        public void sale(a){
            / / lock
            lock.lock();

            try{
                //do something business code
                if(num<=0)return;
                System.out.println(Thread.currentThread().getName()+"Buy the number"+(num--)+"One ticket, the rest."+num+"Ticket");
            }catch(Exception e){
                e.printStackTrace();
            }finally{ lock.unlock(); }}}Copy the code

The difference between synchronized and lock

  1. Synchronized is a built-in Java keyword, and Lock is a Java class
  2. Synchronized cannot determine the status of obtaining a Lock. Lock can determine whether a Lock is obtained
  3. Synchronized willAutomatic releaseLock, lock must beHand releaseThe lock! If the lock is not released,A deadlock
    • If an exception occurs in Synchronized, the lock is automatically released
  4. Synchronized thread 1 (acquire lock, block), thread 2 (wait, fool, etc.); Lock will not necessarily wait;
    • lock.tryLock()Try to get the lock. If you can’t get it, turn around and leave
  5. Synchronized can reentrant, can not interrupt, unfair; Lock, reentrant Lock, judge Lock, not fair (can set yourself)
  6. Synchronized is good for locking a small number of code synchronization problems, Lock is good for locking a large number of synchronization code!

Producer and consumer issues

Interviews: singleton, sorting algorithm, production consumer, deadlock

Older synchronized implementation

When num is 0, the consumer waits and the producer generates the message

When num>=0, producers wait and consumers consume

Let’s take a look at the problem code

    public static void main(String[] args) throws InterruptedException {
        Data data = new Data();
        new Thread(()->{
            for(int i=0; i<100; i++){try {
                    data.pro();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"ProducerA").start();

        new Thread(()->{
            for(int i=0; i<100; i++){try {
                    data.con();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"ConsumerA").start();

    }
    static class Data{
        private int num=0;

        public synchronized void pro(a) throws InterruptedException {
            if(num! =0){
                System.out.println(Thread.currentThread().getName()+"Waiting");
                this.wait();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"The producer produced a message, and num="+num);
            this.notifyAll();
        }
        public synchronized void con(a) throws InterruptedException {
            if(num==0){
                System.out.println(Thread.currentThread().getName()+"Waiting");
                this.wait();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"Consumer consumes a message, num="+num);
            this.notifyAll(); }}Copy the code

Does the code work correctly at this point? It does

What if we put multiple producers and consumers?

        new Thread(()->{
            for(int i=0; i<100; i++){try {
                    data.pro();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"ProducerA").start();

        new Thread(()->{
            for(int i=0; i<100; i++){try {
                    data.pro();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"ProducerB").start();

        new Thread(()->{
            for(int i=0; i<100; i++){try {
                    data.con();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"ConsumerA").start();

        new Thread(()->{
            for(int i=0; i<100; i++){try {
                    data.con();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"ConsumerB").start();
Copy the code

And you can see that there’s a very high probability of something going wrong

The ConsumerA consumer consumes a message, num=-92, ConsumerA consumer consumes a message, num=-93, and num=-94, Num =-95 ConsumerA The consumer consumes a message, num=-96 The consumer consumes a message, num=-97 the ProducerB producer produces a message, num=-96

Is waiting for

This is a false wake up

See the API documentation for Object’s wait method

Threads can also wake up without being notified, interrupted or timed out, a so-called false wake up

For example, if an item has no goods and suddenly enters an item, all threads are awakened, but only one person can buy it, so everyone else is falsely awakened and cannot acquire the lock of the object

Although this rarely happens in practice, the application must guard against it by testing the condition that should cause the thread to wake up, and continue to wait if the condition is not met.

In other words, waiting should always be in the loop

Why is there a false wake up in the if block?

Using wait in an if block is dangerous because once the thread is woken up and the lock is acquired, it no longer evaluates the if condition and executes code outside the if block

Therefore, it is advisable to use the while loop whenever you need to make a conditional judgment and then wait

	synchronized (obj) {
         while(<condition does not hold>) obj.wait(timeout); .// Perform action appropriate to condition
     } 
Copy the code

So we changed the original code to if and changed it to while

        public synchronized void pro(a) throws InterruptedException {
            while(num! =0){
                System.out.println(Thread.currentThread().getName()+"Waiting");
                this.wait();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"The producer produced a message, and num="+num);
            this.notifyAll();
        }
        public synchronized void con(a) throws InterruptedException {
            while (num==0){
                System.out.println(Thread.currentThread().getName()+"Waiting");
                this.wait();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"Consumer consumes a message, num="+num);
            this.notifyAll();
        }
Copy the code

Producer and consumer implementations of the JUC version

Use the Lock and Condition interfaces

The Lock object is instantiated using ReentrantLock

The condition object is obtained using lock.newcondition ()

The Condition implementation can provide the behavior and semantics of Object monitor methods, such as guaranteed order of notifications, or not required when they are executed

lock

A Condition instance is essentially bound to a lock. To get a Condition instance of a particular Condition instance, use it

NewCondition () method

Condition factors the Object monitor methods (wait, notify, and notifyAll) into different objects to get each Object with multiple wait sets by locking them together with any combination of effects.

Lock replaces the use of synchronized methods and statements, and Condition replaces the use of object monitor methods.

    static class Data{
        private int num=0;

        // Get locks instead of wait and notify
        Lock lock=new ReentrantLock();
        // Obtain condition instead of synchronized
        Condition condition = lock.newCondition();

        public void pro(a){
            lock.lock();
            try{

                while(num! =0){
                    System.out.println(Thread.currentThread().getName()+"Waiting");
                    / / wait for
                    condition.await();
                }
                num++;
                System.out.println(Thread.currentThread().getName()+"The producer produced a message, and num="+num);
                / / wake
                condition.signalAll();

            }catch(Exception e){
                e.printStackTrace();
            }finally{ lock.unlock(); }}public void con(a){
            lock.lock();
            try{
                while (num==0){
                    System.out.println(Thread.currentThread().getName()+"Waiting");
                    condition.await();
                }
                num--;
                System.out.println(Thread.currentThread().getName()+"Consumer consumes a message, num="+num);
                condition.signalAll();
            }catch(Exception e){
                e.printStackTrace();
            }finally{ lock.unlock(); }}}Copy the code

One might ask: Synchronized is more concise, but there is also a condition, which is more troublesome.

Of course not

Difference between Lock+Condition and synchronized

Setting up multiple Condition monitors enables precise notification and wake up of threads

Personal understanding: wait when you don’t need, wake up when you need

Accurate awakening of Condition monitor

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

/ * * *@Author if
 * @Description: Accurate awakening using Condition *@Date 2021-11-05 上午 12:04
 */
public class Test03 {
    public static void main(String[] args) {
        Data data=new Data();
        new Thread(()->{
            for(int i=0; i<10;i++){
                data.soutA();
            }
        },"A").start();
        new Thread(()->{
            for(int i=0; i<10;i++){
                data.soutB();
            }
        },"B").start();
        new Thread(()->{
            for(int i=0; i<10;i++){
                data.soutC();
            }
        },"C").start();
    }
    static class Data{

        private Lock lock=new ReentrantLock();
        private Condition condition1=lock.newCondition();
        private Condition condition2=lock.newCondition();
        private Condition condition3=lock.newCondition();
        private int num=1;

        public void soutA(a){
            lock.lock();
            try{
                // condition1 waits when num is not 1
                while(num! =1){
                    condition1.await();
                }
                System.out.println("AAAAAA");
                num=2;
                // Call condition2 to export B
                condition2.signal();
            }catch(Exception e){
                e.printStackTrace();
            }finally{ lock.unlock(); }}public void soutB(a){
            lock.lock();
            try{
                // condition2 waits when num is not 2
                while(num! =2){
                    condition2.await();
                }
                System.out.println("BBBBBB");
                num=3;
                // Call condition3 to condition3
                condition3.signal();
            }catch(Exception e){
                e.printStackTrace();
            }finally{ lock.unlock(); }}public void soutC(a){
            lock.lock();
            try{
                Condition3 = condition3; // condition3 = condition3
                while(num! =3){
                    condition3.await();
                }
                System.out.println("CCCCCC");
                num=1;
                // Call condition1 to output A
                condition1.signal();
            }catch(Exception e){
                e.printStackTrace();
            }finally{ lock.unlock(); }}}}Copy the code

The results of

AAAAAA BBBBBB CCCCCC AAAAAA BBBBBB CCCCCC

The condition also has awaitNanos timeout wait and awaitUntil timeout wait, as described below in ArrayBlockingQueue

Eight lock problem

1. Synchronized is the lock object synchronization

import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @DescriptionTimeunit.seconds.sleep (1) was not added to the main thread; Before, the output order? Timeunit.seconds.sleep (4) was not added to the mail method; Before, the output order? Timeunit.seconds.sleep (4); timeunit.seconds. After the output order? Synchronized does not release a lock until the code is finished@Date 2021-11-05 上午 12:20
 */
public class Test1 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.mail();
        },"A").start();

        try{
            // The main thread sleeps
            TimeUnit.SECONDS.sleep(1);
        }catch(Exception e){
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();
    }

    static class Phone{
        Synchronized locks the caller (instantiated object)
        public synchronized void mail(a){
            try{
                // The lock is not released when it sleeps
                TimeUnit.SECONDS.sleep(4);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println("Send an email");
        }
        public synchronized void call(a){
            System.out.println("Make a call"); }}}Copy the code

2. Synchronized does not synchronize with common methods

import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @DescriptionSynchronized (mail); synchronized (call); synchronized (mail); synchronized (call); synchronized (mail)@Date 2021-11-05 上午 12:29
 */
public class Test2 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        /** * question: If there are two phone methods, mail and call methods are synchronized. In the normal order, of course, because the callers of synchronized locks, that is, instantiate objects, and there are two distinct objects */
// Phone phone1 = new Phone();
// Phone phone2 = new Phone();
// new Thread(()->{
// phone1.mail();
// },"A").start();
// try{
// //main The thread is hibernated
// TimeUnit.SECONDS.sleep(1);
// }catch(Exception e){
// e.printStackTrace();
/ /}
// new Thread(()->{
// phone2.call();
// },"B").start();


        new Thread(()->{
            phone.mail();
        },"A").start();

        try{
            // The main thread sleeps
            TimeUnit.SECONDS.sleep(1);
        }catch(Exception e){
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();
    }

    static class Phone{
        Synchronized locks the caller (instantiated object)
        public synchronized void mail(a){
            try{
                // The lock is not released when it sleeps
                TimeUnit.SECONDS.sleep(4);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println("Send an email");
        }
        // There is no synchronization lock
        public void call(a){
            System.out.println("Make a call"); }}}Copy the code

3. Lock Class synchronized

import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @Description: * * problem: mail and call method to static static method, call order * the answer: the text first, then the Phone, because at this time to lock the Phone. The class, rather than instantiation object * * problem: instantiate two objects, phone1 and phone2, call order * the answer: Again, SMS first and then call, locking Phone. Class instead of instantiating object * * *@Date 2021-11-05 上午 12:37
 */
public class Test3 {
    public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.mail();
        },"A").start();

        try{
            // The main thread sleeps
            TimeUnit.SECONDS.sleep(1);
        }catch(Exception e){
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();


        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(()->{
            phone1.mail();
        },"A").start();

        try{
            // The main thread sleeps
            TimeUnit.SECONDS.sleep(1);
        }catch(Exception e){
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();

    }

     static class Phone{
        //synchronized locks the class, and it doesn't matter if the object is different, because multiple objects have the same class
        public static synchronized void mail(a){
            try{
                TimeUnit.SECONDS.sleep(4);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println("Send an email");
        }
        public static synchronized void call(a){
            System.out.println("Make a call"); }}}Copy the code

4. Static synchronized and common synchronized locks

import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @DescriptionMail is a static synchronized method, and call is a common synchronized method. * the answer: * The call lock is different from the mail's class lock, so it does not need to synchronize. * The call lock is different from the mail's class lock. Call * * is executed@Date 2021-11-05 上午 12:46
 */
public class Test4 {
    public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.mail();
        },"A").start();

        try{
            // The main thread sleeps
            TimeUnit.SECONDS.sleep(1);
        }catch(Exception e){
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();


    }

    static class Phone{

        public static synchronized void mail(a){
            try{
                TimeUnit.SECONDS.sleep(4);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println("Send an email");
        }
        public synchronized void call(a){
            System.out.println("Make a call"); }}}Copy the code

conclusion

Synchronized in a block of code or a normal method, locks the caller of the method (instantiated object)

In static methods, the class of the locked class

Object locks are different from class locks, so no synchronization is required

Unsafe List class

The collections we used were all single-threaded, so there was no problem, but many of them were unsafe

For example, we use ArrayList a lot

        // Multithreaded ArrayList insertion error
        / / concurrent modification anomalies: Java. Util. ConcurrentModificationException
        List<String> stringList=new ArrayList<>();
        for(int i=1; i<=100; i++){new Thread(()->{
                stringList.add(UUID.randomUUID().toString().substring(0.5));
                System.out.println(stringList);
            },i+"").start();
        }
Copy the code

Single thread play more, at first glance no problem, but this is in the case of multithreading, there will be concurrent modification exception

How can you optimize it to be thread-safe?

1. Use Vector instead

Vector add, delete, modify, and check are synchronized to make the thread safe

But what about efficiency? We’ll talk about that later

        // Multithreaded Vector does not report errors because synchronized is added to the underlying Vector
        List<String> stringVector=new Vector<>();
        for(int i=1; i<=100; i++){new Thread(()->{
                stringVector.add(UUID.randomUUID().toString().substring(0.5));
                System.out.println(stringVector);
            },i+"").start();
        }
Copy the code

2. Use Collections to synchronizedList

Use the Collections. SynchronizedList () method to convert ordinary list thread safe list

        // What if I want to use a safe list
        / / use the Collections. SynchronizedList ordinary list into thread safe list
        List<String> stringList=new ArrayList<>();
        List<String> synchronizedList = Collections.synchronizedList(stringList);
        for(int i=1; i<=100; i++){new Thread(()->{
                synchronizedList.add(UUID.randomUUID().toString().substring(0.5));
                System.out.println(synchronizedList);
            },i+"").start();
        }
Copy the code

How do you keep thread-safe and efficient

Use JUC’s CopyOnWriteArrayList

JUC: Use CopyOnWriteArrayList to solve concurrency

COW: Copy-on-write, an optimization strategy

The list is uniquely fixed; it is fixed when read by multiple threads, but may be overwritten when written

COW avoids overwriting and prevents data problems

        /** * JUC: Use CopyOnWriteArrayList to handle concurrency */
        List<String> list = new CopyOnWriteArrayList<>();
        for(int i=1; i<=100; i++){new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0.5));
                System.out.println(list);
            },i+"").start();
        }
Copy the code

How did it work out?

Write to copy an array of length +1, insert data at the end, and assign the array to the original array for insertion

For example, insert source code

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally{ lock.unlock(); }}Copy the code

How is CopyOnWriteArrayList better than vector?

Vector’s add, delete, change, and query methods all have synchronized locks, which ensure that each method has to acquire the lock in the case of synchronization, thus reducing performance

The CopyOnWriteArrayList method simply adds a ReentrantLock lock to the add, delete, and modify methods

However, its read method is unlocked, == reads and writes separate ==, so reads are better than vectors

CopyOnWriteArrayList is suitable for concurrency with too many reads and too few writes

Unsafe Set class

It’s pretty much the same as the list above, so I’m going to skip the explanation and post the code

Multithreading error

        / / concurrent modification anomalies: Java. Util. ConcurrentModificationException
        HashSet<String> set = new HashSet<>();
        for(int i=1; i<=100; i++){new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0.5));
                System.out.println(set);
            },i+"").start();
        }
Copy the code

The solution

1. Turn synchronizedSet

        HashSet<String> set = new HashSet<>();
        Set<String> synchronizedSet = Collections.synchronizedSet(set);
        for(int i=1; i<=100; i++){new Thread(()->{
                synchronizedSet.add(UUID.randomUUID().toString().substring(0.5));
                System.out.println(synchronizedSet);
            },i+"").start();
        }
Copy the code

2. Use CopyOnWriteArraySet

        Set<String> set = new CopyOnWriteArraySet<>();
        for(int i=1; i<=100; i++){new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0.5));
                System.out.println(set);
            },i+"").start();
        }
Copy the code

A quick explanation of the implementation of HashSet

The implementation of HashSet is not necessarily a bonus for saying it, but a penalty for not saying it

It’s essentially a HashMap of new, and then the Object of new is the value of the HashMap, and the parameter of add is the key

Because it’s a hash algorithm, the HashSet is unordered

Since the key cannot be repeated, the elements of a HashSet cannot be repeated

    private transient HashMap<E,Object> map;
	private static final Object PRESENT = new Object();

	public HashSet(a) {
        map = new HashMap<>();
    }

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
Copy the code

Unsafe Map class

The HashMap we often use in a single thread is also unsafe in multiple threads

        / / concurrent modification anomalies: ConcurrentModificationException
        HashMap<String, String> map = new HashMap<>();
        for(int i=1; i<=100; i++){new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0.5));
                System.out.println(map);
            }).start();
        }
Copy the code

1. Use Hashtable instead

        Map<String, String> hashtable = new Hashtable<>();
        for (int i = 1; i <= 100; i++) {
            new Thread(() -> {
                hashtable.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0.5));
                System.out.println(hashtable);
            }).start();
        }
Copy the code

As with Vector instead of ArrayList, synchronized is a simple and crude way to ensure thread-safety, though it may be less efficient

2. Turn synchronizedMap

        HashMap<String, String> map = new HashMap<>();
        Map<String, String> synchronizedMap = Collections.synchronizedMap(map);
        for (int i = 1; i <= 100; i++) {
            new Thread(() -> {
                synchronizedMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0.5));
                System.out.println(synchronizedMap);
            }).start();
        }
Copy the code

3. Use ConcurrentHashMap

Using Java. Util. Concurrent. ConcurrentHashMap

Concurrent Hashmaps, which ensure thread-safety but also efficiency, are recommended

If you’re not familiar with ConcurrentHashMap, check out my ConcurrentHashMap.

        Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
        for (int i = 1; i <= 100; i++) {
            new Thread(() -> {
                concurrentHashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0.5));
                System.out.println(concurrentHashMap);
            }).start();
        }
Copy the code

Into the Callable

A task that returns results and may throw an exception. The implementer defines a single method with no arguments, called call

Callable interfaces are similar to Runnable in that they are designed for classes whose instances might be executed by another thread

However, A Runnable does not return results and cannot throw checked exceptions

  • You can have a return value
  • You can throw an exception
  • Methods different
    • A Runnable is run ()
    • Callable is call ()

The old version created threads in two ways

1.extends Thread

public static void main(String[] args) {
        // The Thread class inherits Thread from the run method and starts it
        new MyThread().start();
    }

    static class MyThread extends Thread{
        @Override
        public void run(a) {
            System.out.println("class MyThread extends Thread"); }}Copy the code

2. Implement the Runnable interface

public static void main(String[] args) {
        // The Thread class implements Runnable run, which starts the Thread by putting the instantiated object into the Thread argument
        new Thread(new MyRun()).start();
    }
    static class MyRun implements Runnable{
        @Override
        public void run(a) {
            System.out.println("class MyRun implements Runnable"); }}Copy the code

Create a thread using Callable

We need an adaptation class here, FutureTask

This class implements RunnableFuture, FutureTask

implements RunnableFuture

The RunnableFuture class extends Runnable, Future

So a Thread(Runnable target) can pass it in

Note: FutureTask.get () can get returned results, but may throw exceptions that need to be caught or thrown

Because it waits for execution to complete before returning, it can block and is best handled last or asynchronously

public static void main(String[] args) {
        /** * Use the Callable thread class to implement Callable to implement the Call () method, which can have a return value, which is defined by the generic type * create FutureTask, Thread(Runnable target) * FutureTask
      
        implements RunnableFuture
       
         * RunnableFuture
        
          extends Runnable, Future
         
           */
         
        
       
      
        MyCall callable = new MyCall();
        / / class
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask).start();
        // Get return result, exception may be thrown
        // It may block because it is waiting for execution to complete, so it is best to leave it at the end, or asynchronous communication to handle it
        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch(InterruptedException | ExecutionException e) { e.printStackTrace(); }}static class MyCall implements Callable<Integer>{
        @Override
        public Integer call(a) throws Exception {
            System.out.println("class MyCall implements Callable<Integer>");
            return 1024; }}Copy the code

The state of the FutureTask

If we pass in two threads with the same FutureTask at this point, will the output be twice?

        MyCall callable = new MyCall();
        / / class
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // What happens if you start two threads with the same futureTask object?
        // The answer is: only one thread will run, and only one output
        // Because the state of FutureTask has changed from initialization NEW to completion state
        // If the run method is not NEW, it returns no execution
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();
Copy the code

The answer is: no, only once!

Why is that?

Let’s look at the source code

You can see that FutureTask has a state for state variables and a number of constants of type int for specific states

Here we focus only on NEW and COMPLETING

    public class FutureTask<V> implements RunnableFuture<V> {
    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;
Copy the code

In the constructor, state is given NEW by default

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
Copy the code

The first thread comes in

In the run method, ran is set to trueIf (RAN) set(result) after the callable call method is executed.

In set method UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)

Change the NEW state into becoming well

That is to say, the FutureTask has completed its duty COMPLETING those years well

The next thread comes in and determines state! = NEW, return directly

So after executing once, no other thread can continue to execute run, the call method of the Callable

So you can conclude that normally a FutureTask can only perform one call

    public void run(a) {
        if(state ! = NEW || ! UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if(c ! =null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if(ran) set(result); }}finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if(s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); }}Copy the code

Commonly used helper classes

CountDownLatch subtraction counter

java.util.concurrent.CountDownLatch

Subtractive counter

A synchronization helper class that allows one or more threads to wait until a set of operations performed in another thread is complete

Can be used to enforce certain threads

CountDownLatch is initialized with the given count. The await method blocks until the current count reaches zero due to a call to the countDown() method, after which all waiting threads are released and any subsequent await calls return immediately. This is a one-time phenomenon – the count cannot be reset. If you need to reset the versions of the count, consider using CyclicBarrier.

A CountDownLatch is A universal synchronization tool that can be used for A variety of purposes. A CountDownLatch for a CountDownLatch acts as a simple open/close latch, or door: all threads call await and wait at the door until the thread called countDown() opens. A CountDownLatch initialization N can be used to do a thread wait until N threads have completed an operation or some action has been completed N times.

One useful property of CountDownLatch is that it does not require the call to the countDown thread to wait until the count reaches zero to continue, it simply blocks any thread from going through the await until all threads can go through

import java.util.concurrent.CountDownLatch;

/ * * *@Author if
 * @Description: countDown lock (subtractive counter) * every time the countDown method is called -1 * blocks until the total is not zero * can be used for thread coercion (because not executing would block) *@Date 2021-11-06 上午 12:00
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //count Indicates the total number of entries
        // With the await method, no more code is executed down until the countdown is over
        CountDownLatch countDownLatch = new CountDownLatch(12);

        for(int i=1; i<=6; i++){try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("The first"+i+"CountDown for second execution");
            countDownLatch.countDown();
// new Thread(()->{
// system.out.println (thread.currentThread ().getName()+" Thread is gone ");
// // Quantity -1
// countDownLatch.countDown();
// },i+"").start();
        }
        // Wait for the counter to zero, and then proceed down
        countDownLatch.await();
        System.out.println("Closed"); }}Copy the code

CyclicBarrier addition counter

java.util.concurrent.CyclicBarrier

Addition counter

A synchronization helper class that allows a group of threads to all wait for each other to reach a common barrier point

Can be used to force waits on certain threads

Loop blocking is useful in programs involving fixed size threads that must occasionally wait for each other. A barrier is called a loop because it can be reused after the waiting thread is released.

A CyclicBarrier supports an optional Runnable command that runs once for each barrier point, after the last thread in the party arrives, but before any thread is released. This barrier operation is useful for updating the shared state before either party proceeds.

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/ * * *@Author if
 * @Description: loop barrier (addition counter) * blocks the current thread with an await method * blocks until the number of times the await method is executed fails to reach the argument parties passed by the constructor *@Date 2021-11-06 上午 12:08
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        // Collect 7 dragon balls to summon the divine dragon
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("Collect 7 dragon balls and summon the Dragon successfully.");
        });
        for(int i=1; i<=7; i++){ Thread.sleep(500);
            int finalI = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Collect the first"+finalI+"Dragon Ball");
                try {
                    // Wait until you have waited 7 times
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },i+"").start(); }}}Copy the code

Semaphore Semaphore

A counting semaphore, conceptually, maintains a set of licenses.

If necessary, each acquire() blocks until a license is available before it can be used. Each release() adds a license, potentially freeing blocking receivers

However, no actual license object is used; Semaphore only keeps counts of available quantities and executes them accordingly

Semaphores are usually used to limit the number of threads, not to access some (physical or logical) resource

We assume that there are three parking Spaces and six cars that need to park, so only three cars can get in and the rest have to wait until the space is empty

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/ * * *@Author if
 * @DescriptionPermits: Semaphore * parameter permits, which can be used to limit the number of threads *@Date 2021-11-06 上午 12:17
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        / / permits licenses
        // We assume that there are 3 parking Spaces and 6 cars to park
        Semaphore semaphore = new Semaphore(3);

        for(int i=1; i<=6; i++){new Thread(()->{
                try{
                    // Acquire a license
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"Thread grabs parking space.");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"Thread leaving parking space");
                }catch(Exception e){
                    e.printStackTrace();
                }finally{
                    //release release license
                    semaphore.release();
                }
            },i+"").start(); }}}Copy the code