JUC concurrent programming

First, basic knowledge

What is JUC: In Java, the threading part is a big focus, and this article is all about threads. JUC stands for java.util.Concurrent toolkit. This is a toolkit for working with threads, which started with JDK 1.5

Processes and threads

Process: The basic unit of program execution and resource allocation. Each program has at least one process and each process has at least one thread. A process has a separate memory unit during execution, while multiple threads share memory resources, reducing the number of switches and making it more efficient.

Thread: An entity of a process. It is the basic unit of CPU scheduling and dispatch. It is a basic unit smaller than a program that can run independently. Multiple threads in the same process can execute concurrently.

Daemon threads and user threads

User thread **:** ordinary threads, custom threads

Daemon threads, or daemons, are service threads, specifically services other threads, the common Daemon thread :CG garbage collector threads

Several ways to create threads

Method 1: Inherit Thread class to create a Thread, overwrite the run method;

public class myThread extends Thread{
   @Override
    public void run(a) {
        System.out.println("This is my thread.");
    }
    public static void main(String[] args) {
        myThread myThread = newmyThread(); myThread.start(); }}Copy the code

Method 2: implement the Runnable interface to create threads;

//1 Use traditional methods
public class myThread implements Runnable{
    @Override
    public void run(a) {
        System.out.println("This is my thread.");
    }
     public static void main(String[] args) {
        myThread runnable = new myThread();
        Thread thread = newThread(runnable); thread.start(); }}//2 is represented by lambda
public class Main(a){
    public static void main(String[] args) {
        new myThread(()->{
            System.out.println("This is my thread."); }).start(); }}Copy the code

Method 3: Create threads with Callable and Future.

public class CallableDemo implements Callable {
    @Override
    public Integer call(a) throws Exception {
        System.out.println(Thread.currentThread().getName());
        return 1024; }}class Main{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable callable=new CallableDemo();
        FutureTask futureTask=new FutureTask<>(callable);
        new Thread(futureTask,"AA").start(); System.out.println(futureTask.get()); }}Copy the code

Method 4: Create a thread from a thread pool. Executors

Use either of the following methods because thread pools don’t allow for Executors. Go through ThreadPoolExecutor.

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

  1. FixedThreadPool and SingleThreadPool: The allowed request queue length is integer. MAX_VALUE, which may accumulate a large number of requests and result in OOM
  2. CachedThreadPool and ScheduledThreadPool: The number of threads allowed to be created is integer. MAX_VALUE, which may create a large number of threads, resulting in OOM
public static void main(String[] args) {
    // A pool of threads
    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    // One thread per pool
    //ExecutorService threadPool = Executors.newSingleThreadExecutor();
    // A pool of expandable threads
    //ExecutorService threadPool = Executors.newCachedThreadPool();
    try {
        for (int i = 0; i < 10; i++) {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName()+"Transacted business"); }); }}catch (Exception e){
        e.printStackTrace();
    }finally{ threadPool.shutdown(); }}Copy the code

By source can know the above three methods to create a thread pool is a new a ThreadPoolExecutor (nThreads, nThreads, 0 l, TimeUnit MILLISECONDS, new LinkedBlockingQueue ()); Here we know the seven parameters of the thread pool;

Thread pool parameters:

Thread seven parameters describe
int corePoolSize Specifies the number of threads in the thread pool
int maximumPoolSize Specifies the maximum number of threads in a thread pool
long keepAliveTime When the number of thread pools exceeds corePoolSize, the excess idle thread lifetime (how soon the number of idle threads exceeding corePoolSize will be destroyed)
TimeUnit unit The unit of keepAliveTime
BlockingQueue workQueue Block the queue. A task that has been submitted but not yet executed
ThreadFactory threadFactory Thread factory, used to create threads, usually using the default
RejectedExecutionHandler handler Rejection strategy, how to reject when there are too many tasks to handle

Thread pool workflow:

When invoking the execute (), came to A, B to go through 1 first permanent thread in the thread, how again next three request C, D, E, through 2 into the blocking queue, if the F, G, H request, will go to get the most other threads in A thread. If another Mx thread request comes, it will enter its rejection policy

According to execute’s source code comment: If we can’t queue tasks, we try adding a new thread. If it fails, we know we are closed or saturated and therefore reject the task. So it feels like the incoming request is going to fetch another thread from the maximum number of threads

Four rejection policies built into the JDk

When the maximum number of threads is exceeded, a rejection policy is implemented, and there are four basic rejection policies built into the JDK

AbortPolicy (default) : an exception is thrown, does not perform this task, and throw a runtime exception RejectedExecutionException directly, the default block strategy for Java thread pool. Remember that it interrupts the caller’s processing, so you need a try catch or the program will simply exit

CallerRunsPolicy: This task is handled by the calling thread. This strategy does not discard tasks or throw exceptions, but rather pushes some tasks back to the caller, thereby reducing the traffic of new tasks

DiscardOldestPolicy: Discards the first (oldest) task in the queue and then tries to execute the task again (repeat the process).

DiscardPolicy: Silently discards the task without triggering any action. This strategy is rarely used

Custom thread pools

Create custom threads using ThreadPoolExecutor, as in the thread pool created in the workflow example above

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
                2.5.2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        try {
            for (int i = 0; i < 10; i++) {
                myThreadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"Transacted business"); }); }}catch (Exception e){
            e.printStackTrace();
        }finally{ myThreadPool.shutdown(); }}}Copy the code

Result: An exception was thrown because the thread request (10) exceeded the maximum number of threads (5) and blocked the queue (3), and because the rejection policy was AbortPolicy

RunnableCallableWhat’s the difference?

  1. The return value of the run() method in the Runnable interface is void, and all it does is execute the code in the run() method;

  2. The Call () method in the Callable interface, which returns a value, is a generic type that can be used in conjunction with Future and FutureTask to retrieve the result of asynchronous execution.

Thread state

There are six states defined in the Thread source code (line 1742 of the Thread source code can be viewed for yourself) : New, runnnable, blocked, waiting, time waiting, and terminated.

public enum State {

    NEW,

    RUNNABLE,

    BLOCKED,

    WAITING,

    TIMED_WAITING,

    TERMINATED;
}
Copy the code

What is the difference between sleep and wait?

1. The thread executing the sleep() method gives up CPU (which can then perform other tasks), and the CPU returns to the thread after the specified time of sleep(note: the sleep method only gives up CPU, but does not release synchronous resource lock). Wait () allows a thread to temporarily release the resource lock so that other threads that are waiting for the resource can acquire the resource and run it. If notify() is called, the thread that called wait() will be released from its wait state and can compete for the resource lock. And it gets executed. (Note that notify wakes up a person who is asleep and does not assign tasks to them. This means that notify grants the thread that called WAIT the right to resume scheduling.) Sleep: do not release the CPU lock, wait: release the CPU lock, and notify() is the only way to obtain the synchronous resource lock again

Sleep () can be used anywhere, while wait() can only be used in synchronized methods or blocks;

3. Sleep () is a method of Thread class, which will suspend the specified time of this Thread, but the monitoring is still maintained, and the object lock will not be released. Wait () is an Object method. The lock pool is entered only when notify()/notifyAll() is invoked to wake up the specified thread or all threads. The lock pool is entered only when the Object lock is not obtained again.

Tube side

A monitor ensures that only one process is active in a pipe at a time. That is, operations defined in a pipe are called by only one process at a time (implemented by the compiler). However, this does not guarantee that the process will execute in the designed order

Synchronization in the JVM is implemented based on inbound and outbound Monitor objects. Each object has a Monitor object, which is created and destroyed along with the Java objects

The thread of execution must first hold the pipe object before it can execute the method. When the method completes, it will release the pipe. The method will hold the pipe while executing

Synchronized and Lock

Synchronized

Synchronized is a Java keyword and a type of synchronization lock. It modifies the following objects

  1. Modifies a block of code, called a synchronous block, that acts on code enclosed in curly braces {} and on the object that calls the block.

  2. Modify a method. The modified method is called a synchronous method. The scope of the modified method is the whole method and the object is the object that calls the method. Although synchronized can be used to define methods, synchronized is not part of the method definition, and therefore the synchronized keyword cannot be inherited. If a method in a parent class uses the synchronized keyword and a method in a subclass overrides it, the method in the subclass is not synchronized by default, and you must explicitly add the synchronized keyword to the method in the subclass. Of course, you can also call the parent method in a subclass method, so that even though the method in the subclass is not synchronized, the subclass calls the parent’s synchronized method, so that the method in the subclass is synchronized.

  3. Modify a static method that applies to the entire static method and to all objects of the class.

The basis of synchronized: Every object in Java can be used as a lock in one of the following three forms.

  1. For normal synchronous methods, the lock is the current instance object.
  2. For statically synchronized methods, the lock is the class object of the current class.
  3. For synchronized method blocks, the lock is an object configured in synchonized parentheses.

Lock

The Lock Lock implementation provides a wider range of Lock operations than can be obtained using synchronized methods and statements. They allow for more flexible structures, may have very different attributes, and may support multiple associated conditional objects. Lock offers more functionality than synchronized.

newCondition

Condition class

In JDK5, for example, multiple Condition(object monitor) instances can be created in a Lock object. Thread objects can be registered in a given Condition, allowing selective thread notification and greater flexibility in scheduling threads.

When using notify/notifyAll(), the threads to be notified are randomly selected by the JVM. Condition, however, can optionally be notified. There will be some efficiency savings.

The lock.lock() method must be called to get the synchronization monitor before the condition.await () method can be used

Synchronized is used together with wait()/notify() to implement the wait/notification mode. The Lock Lock newContition() method returns a Condition object. The Condition class can also implement the wait/notification mode. With notify(), the JVM wakes up a waiting thread at random. Condition can be used for selective notification. Condition has two common methods:

  1. Await () causes the current thread to wait and release the lock, and when another thread calls signal(), the thread regains the lock and continues execution.
  2. Signal () is used to wake up a waiting thread.

Note: The thread is also required to hold the associated Lock Lock before calling the await()/signal() method of the Condition, which is released after calling await(), and wakes up a thread from the current Condition waiting queue after singal(). The awakened thread attempts to acquire the lock and resumes execution once the lock has been acquired.

ReentrantLock

ReentrantLock stands for “ReentrantLock.” The concept of ReentrantLock will be discussed later. ReentrantLock is the only class that implements the Lock interface, and ReentrantLock provides more methods

Case study:

public class ThreadDemo {
    private int number = 0;
    / / create the Lock
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    
    public void increment(a) {
        try {
            this.lock.lock();
            while(number ! =0) {
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + ":" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}public  void decrement(a) {
        try {
            this.lock.lock();
            while (number == 0) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + ":" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}}Copy the code
public static void main(String[] args) {
    ThreadDemo threadDemo = new ThreadDemo();
    new Thread(() -> {
        for (int i = 0; i < 10; i++) { threadDemo.increment(); }},"AA").start();
    new Thread(() -> {
        for (int i = 0; i < 10; i++) { threadDemo.decrement(); }},"BB").start();
    new Thread(() -> {
        for (int i = 0; i < 10; i++) { threadDemo.increment(); }},"CC").start();
    new Thread(() -> {
        for (int i = 0; i < 10; i++) { threadDemo.decrement(); }},"DD").start();
}
Copy the code

Lock is distinguished from Synchronized

  1. Lock is not built into the Java language, and synchronized is a Java language keyword and therefore a built-in feature. Lock is an interface whose implementation class implements synchronous access.
  2. There is a big difference between Lock and synchronized. Using synchronized does not require the user to manually release the Lock. When the synchronized method or synchronized code block is executed, the system will automatically let the thread release the Lock. A Lock must be manually released by the user. If the Lock is not actively released, a deadlock may occur.

Fair and unfair locks

Fair lock: multiple threads acquire locks in the same order as they apply for locks, and the thread directly enters the queue to queue. The thread is always the first in the queue to obtain locks

  • Advantages: All threads get resources and do not starve to death in the queue.
  • Disadvantages: Throughput drops dramatically, all but the first thread in the queue blocks, and it is expensive for the CPU to wake up blocked threads.

Source code analysis:

TryAcquire is an abstract method, which is the implementation principle of fair and unfair.

AddWaiter adds the current thread node to the wait queue. A fair lock is a strict queue to fetch subsequent values after the lock is released, whereas a non-fair lock has a great advantage over new threads.

AcquireQueued attempts to acquire the lock or block the current thread in multiple loops.

SelfInterrupt if a Thread interrupts during blocking, thread.currentthread () is called to interrupt the currentThread.

The method of AbstractQueuedSynchronizer class:

public final void acquire(int arg) {
    if(! tryAcquire(arg) &&//tryAcquire returns false and joins the queue via the addWaiter thread
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
Copy the code

Already the class:



static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock(a) {
        acquire(1);
    }

    /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();// Get the current thread
        int c = getState();// Lock occupied state
        if (c == 0) { // If the lock occupation status is 0, it means that the lock is released and can be obtained
            if(! hasQueuedPredecessors() &&/ /! Hasqueued24 Whether to wait on a queue
                compareAndSetState(0, acquires)) {If state is 0, preempt the lock directly and set the lock state to 1
                setExclusiveOwnerThread(current);// Set the thread that holds the lock
                return true; }}else if (current == getExclusiveOwnerThread()) {// The current thread is holding the lock
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);/ / lock
            return true;
        }
        return false;// Could not get the lock}}Copy the code

Unfair lock: When multiple threads try to obtain the lock, they will directly try to obtain it. If they fail to obtain the lock, they will enter the waiting queue. If they can obtain the lock, they will directly obtain the lock.

  • Advantages: can reduce the CPU wake up thread overhead, the overall throughput efficiency will be higher, CPU does not have to wake up all threads, will reduce the number of wake up threads.
  • Disadvantages: As you may have noticed, this can cause a thread in the middle of the queue to remain without a lock or for a long time, leading to starvation.

Source code analysis:

Already the class

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */
    final void lock(a) {
        if (compareAndSetState(0.1))If state is 0, preempt the lock directly and set the lock state to 1
            setExclusiveOwnerThread(Thread.currentThread());// Set the thread that holds the lock
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        returnnonfairTryAcquire(acquires); }}Copy the code

Reentrant lock

A reentrant lock is a thread-by-thread lock. When one thread acquires an object lock, that thread can acquire the lock again, while other threads cannot. Synchronized and ReentrantLock are reentrant locks. One of the points of reentrant locks is to prevent deadlocks

Presentation:

// What does reentrant lock mean
public class WhatReentrant2 {
	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();
		
		new Thread(new Runnable() {
			@Override
			public void run(a) {
				try {
					lock.lock();
					System.out.println(The first time the lock is acquired, the lock is: + lock);

					int index = 1;
					while (true) {
						try {
							lock.lock();
							System.out.println("The first" + (++index) + "Second fetch lock, this lock is:" + lock);
							
							try {
								Thread.sleep(new Random().nextInt(200));
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
							
							if (index == 10) {
								break; }}finally{ lock.unlock(); }}}finally{ lock.unlock(); } } }).start(); }}Copy the code

Result: The lock of this object can be acquired repeatedly

A deadlock

A deadlock is a phenomenon in which two or more processes are blocked during execution, either by competing for resources or by communicating with each other, and cannot proceed without external action. The system is said to be in a deadlock state or a deadlock occurs in the system. These processes that are always waiting for each other are called deadlocked processes

Four necessary conditions for deadlock

  1. Mutual exclusion: A process requires exclusive control over allocated resources (such as printers), that is, a resource is owned by only one process at a time. If another process requests the resource, the requesting process can only wait.
  2. Inalienable conditions: A resource acquired by a process cannot be seized by another process before it is fully used, that is, it can only be released by the process that acquired the resource itself (only voluntarily).
  3. Request and hold condition: a process has held at least one resource, but it makes a new resource request, and the resource has been occupied by another process. In this case, the requesting process is blocked, but it does not release the obtained resource.
  4. Circular waiting condition: There is a circular waiting chain of process resources, each process in the chain has acquired resources are also requested by the next process in the chain.

JUC auxiliary class

CountDownLatc class: 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. (The subtraction device runs until the counter reaches 0)

Case study:

public static void main(String[] args) throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(6);
    for (int i = 1; i <= 6; i++) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" Go out");
            countDownLatch.countDown();  // counter -1
        },String.valueOf(i)).start();
    }
    countDownLatch.await();// Wait for the counter to return to 0 before proceeding
    new Thread(()->{
        System.out.println("close door");
    }).start();
}
Copy the code

Run result: only run the set number of times, can then run

CyclicBarrier class: synchronization AIDS that allow a group of threads to all wait for each other to reach a common barrier point. Loop blocking is useful in programs involving fixed size threads that must occasionally wait for each other. (Multiplicator: continue after increment to set value)

Case study:

public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("Collect 7 dragon balls and summon the dragon.");
        });

        for (int i = 1; i <= 7; i++) {
            new Thread(()->{
                System.out.println("It's all set."+Thread.currentThread().getName()+"Dragon Ball");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch(BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); }}Copy the code

Run result: only after await() has set the value will it continue to run its own method

Semaphore class: A counting Semaphore. Conceptually, semaphores maintain 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. (Only acquire() can run, while release() can be used, which can be compared to parking Spaces)

Case study:

Public static void main(String[] args) {// Number of threads, limit Semaphore Semaphore = new Semaphore(3); for (int i = 1; i <= 6; i++) { new Thread(()->{ try { semaphore.acquire(); // Get system.out.println (thread.currentThread ().getName()+ ); TimeUnit.SECONDS.toSeconds(new Random().nextInt(5)); System.out.println(thread.currentThread ().getName()+" Exit parking space!!" ); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); }}, string.valueof (I)).start(); }}Copy the code

Running results:

Read-write lock ReentrantReadWriteLock

Read lock: called shared lock

Write lock: Exclusive lock

1. Prerequisites for thread to enter read lock:

  • There are no write locks for other threads
  • There are no write requests, or == there are write requests, but the calling thread and the thread holding the lock are the same (reentrant lock).

2. Prerequisites for thread to enter write lock:

  • There are no read locks for other threads
  • There are no write locks for other threads

Read/write locks have three important features:

(1) Fair selection: support unfair (default) and fair lock acquisition methods, throughput is still unfair is better than fair.

(2) Re-entry: Both read and write locks support thread re-entry.

(3) Lock degradation: a write lock can be degraded to a read lock by following the sequence of obtaining a write lock, obtaining a read lock, and then releasing the write lock.

Case study:

class MyCache{
    private volatile HashMap<String, String> map=new HashMap<>();
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public void put(String key,String value){
        readWriteLock.writeLock().lock();
        
        System.out.println(Thread.currentThread().getName()+"===> Write operation");
        TimeUnit.MICROSECONDS.toSeconds(1000);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+===> Write complete operation);
        
        readWriteLock.writeLock().unlock();
    }
    public void get(String key){
        readWriteLock.readLock().lock();
        
        System.out.println(Thread.currentThread().getName()+"===> Reading operation");
        TimeUnit.MICROSECONDS.toSeconds(1000);
        map.get(key);
        System.out.println(Thread.currentThread().getName()+"===> Read complete operation"); readWriteLock.readLock().lock(); }}Copy the code
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 1; i <= 5; i++) {
            final int temp=i;
            new Thread(()->{
                myCache.put(String.valueOf(temp),String.valueOf(temp));
                TimeUnit.MICROSECONDS.toSeconds(1000);
                myCache.get(String.valueOf(temp));
            },"Thread"+String.valueOf(i)).start(); }}}Copy the code

Result: a thread is writing while other threads wait for it to finish writing, and then grab CPU resources

Three,Collection is thread-safe

The ArrayList collection thread is not safe

Case study:

Public class ThreadList {public static void main(String[] args) {List arrayList=new arrayList (); for (int i = 0; i < 30; i++) { new Thread(()->{ arrayList.add(UUID.randomUUID().toString()); System.out.println(arrayList); }, "threads" + I). The start (); }}}Copy the code

The result error: ConcurrentModificationException concurrent modification abnormalities

Cause: in system.out.println (arrayList); , a thread is writing data to the arrayList, and a thread is reading the arrayList

The solution

Method 1: Use Vector(not recommended)

public class ThreadList   {
    public static void main(String[] args) {
        Vector arrayList =new Vector();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString());
                System.out.println(arrayList);
            },"Thread"+i).start(); }}}Copy the code

Method two: Use Collections

public class ThreadList   {
    public static void main(String[] args) {
        List<String> arrayList =Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString());
                System.out.println(arrayList);
            },"Thread"+i).start(); }}}Copy the code

Method 3: Use CopyOnWriteArrayList

public class ThreadList   {
    public static void main(String[] args) {
        List<String> arrayList = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString());
                System.out.println(arrayList);
            },"Thread"+i).start(); }}}Copy the code

CopyOnWriteArrayList source code parsing: On add, lock, assign the original array to elements, place a copy of the original elements in newElements, place the add value at the end of newElements, and overwrite or merge newElements

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

HashSet threads are not safe

public static void main(String[] args) {
    HashSet<String> set = new HashSet<>();
    for (int i = 0; i < 30; i++) {
        new Thread(()->{
            set.add(UUID.randomUUID().toString());
            System.out.println(set);
        },"Thread"+i).start(); }}Copy the code

Many times after the result error ConcurrentModificationException

Solution: Use CopyOnWriteArraySet

public static void main(String[] args) {
    Set<String> set = new CopyOnWriteArraySet<>();
    for (int i = 0; i < 30; i++) {
        new Thread(()->{
            set.add(UUID.randomUUID().toString());
            System.out.println(set);
        },"Thread"+i).start(); }}Copy the code

The HashMap thread is not safe

The occurrence of insecurity is the same as several types above

Solution: Use CopyOnWriteArraySet

public static void main(String[] args) {

    Set<String> set = new CopyOnWriteArraySet<>();
    Map<String, String> map=new ConcurrentHashMap<>();
    for (int i = 0; i < 30; i++) {
        String key = String.valueOf(i);
        new Thread(()->{
            map.put(key,UUID.randomUUID().toString().substring(0.8));
            System.out.println(map);
        },"Thread"+i).start(); }}Copy the code