Java multithreading and concurrent programming | synchronization container & Atomic package & CAS algorithm

preface

In the multithreaded environment, many classes we use everyday have thread safety problems, such as ArrayList, HashSet and HashMap. How should we deal with the thread problem in the multithreaded environment? What is the CAS algorithm? Is there any other way to achieve thread safety other than synchronized? Optimistic lock? Pessimistic lock?

Synchronization container

For these collection classes often used in daily development, the JUC toolkit provides us with thread safe classes to replace these classes.

  • ArrayList -- > copyonwritearraylist -- write copy list
  • HashSet -- > copyonwritearrayset -- write copy set
  • HashMap -- > concurrenthashmap -- piecewise lock mapping

CopyOnWriteArrayList

The bottom layer of CopyOnWriteArrayList avoids the concurrency problem in multi-threaded environment by operating "copy".

When a thread operates on the list, it will first add a lock, then copy the value of the current object, re create a new object with length + 1, and then point the reference address to the new ArrayList

add() method 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();
    }
}

CopyOnWriteArraySet

It is similar to CopyOnWriteArrayList, except that the data structure of the underlying data is different

Source code

private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

ConcurrentHashMap

Code example

public class ConcurrentHashMapSample {
    public static int users = 100;//Number of concurrent access users simulated at the same time
    public static int downTotal = 50000; //Total number of real downloads by users
    public static ConcurrentHashMap count = new ConcurrentHashMap() ;//Counter

    public static void main(String[] args) {
        ExecutorService executorService  = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(users);
        for(int i = 0 ; i < downTotal ; i++){
            final Integer index = i;
            executorService.execute(()->{
                //Simulate concurrent access and download of N users through multithreading
                try {
                    semaphore.acquire();
                    count.put(index, index);
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//Turn off scheduling service
        System.out.println("Total downloads:" + count.size());
    }
}

The underlying layer of ConcurrentHashMap divides the original data into a small segment with a length of 2 to the nth power, and then locks it in segments. Multiple threads process it at the same time, which also improves some efficiency under the condition of ensuring safety.

extend

Atomicity

Atomicity means that an operation or multiple operations are either executed completely and the execution process will not be interrupted by any factor, or they will not be executed at all.

Atomic package

  • Atomic package is another Java package specially designed for thread safety under JUC, which contains multiple atomic operation classes
  • Atomic common classes
    • AtomicInteger
    • AtomicIntergerArray
    • AtomicBoolean
    • AtomicLong
    • AtomicLongArray

The synchronized keyword ensures the safe accumulation of downloads under multithreading. Here, you can use Atomic atomic classes to complete the optimization through a more lightweight method.

Optimize code

public static AtomicInteger count = new AtomicInteger() ;//Counter
...
//Thread unsafe
public static void add(){
    count.getAndIncrement(); //count++
}

CAS algorithm

Pessimistic lock

  • Lock is the simplest way to do concurrency, and of course, its cost is the highest. An exclusive lock is a pessimistic lock, and synchronized is an exclusive lock. It assumes the worst case and is executed only when it is ensured that other threads will not cause interference. It will cause all other threads that need a lock to hang and wait for the thread holding the lock to release the lock.

Optimistic lock

  • The so-called optimistic lock is to complete an operation without locking each time, assuming that there is no conflict. If the conflict fails, try again until it succeeds. CAS (Compare And Swap) is a well-known lock free algorithm.

Application scenario of Atomic

Although the thread safety mechanism based on CAS is very good and efficient, it should be said that not all thread safety can be implemented in this way. This is only suitable for some requirements with small granularity, such as counters, which are effective. Otherwise, there will be no lock.

Keywords: Java Algorithm data structure Concurrent Programming

Added by Stray_Bullet on Sat, 18 Dec 2021 19:19:51 +0200