This is the 26th day of my participation in the August Text Challenge.More challenges in August

preface

In a multithreaded environment, many of the classes we use everyday have thread safety problems, such as ArrayList, HashSet, HashMap, so how should we handle thread problems in a multithreaded environment? What is the CAS algorithm? Is there another way to achieve thread safety besides synchronized? Optimistic locking? Pessimistic locks?

Synchronization of the container

The JUC toolkit provides thread-safe alternatives to these collection classes that are often used in everyday development.

  • ArrayList --> CopyOnWriteArrayList -- Write copy list
  • HashSet --> CopyOnWriteArraySet -- Write copy set
  • HashMap --> ConcurrentHashMap -- Piecewise lock mapping

CopyOnWriteArrayList

The CopyOnWriteArrayList layer avoids concurrency problems in multithreaded environments by manipulating “copies”.

When a thread operates on a list, it adds a lock, makes a copy of the current object’s value, creates a new object of length +1, and points the reference address to the new ArrayList

Add () method source

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

CopyOnWriteArraySet

This is similar to CopyOnWriteArrayList, except that the underlying data structure stores the data

The 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(); }}Copy the code

ConcurrentHashMap

Code sample

public class ConcurrentHashMapSample {
    public static int users = 100;// The number of concurrent access users simulated at the same time
    public static int downTotal = 50000; // The real total number of 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 N users' concurrent access and download through multiple threads
                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();// Disable the scheduling service
        System.out.println("Total downloads:"+ count.size()); }}Copy the code

The underlying ConcurrentHashMap splits the original data into small segments with a length of 2 to the power of n, and then locks the segments. Multiple threads process the data at the same time, which improves efficiency while ensuring safety.

extension

atomic

Atomicity means that an operation or operations are either all performed without interruption by any factor, or none at all.

Atomic package

  • AtomicPackage isJUCHere’s another one designed specifically for thread safetyJavaPackage containing multiple atomic action classes
  • AtomicCommonly used class
    • AtomicInteger
    • AtomicIntergerArray
    • AtomicBoolean
    • AtomicLong
    • AtomicLongArray

In the previous download cases, synchronized keyword was used to ensure the cumulative security of downloads under multiple threads. This can be done in a more lightweight way using Atomic Atomic classes.

Optimize the code

public static AtomicInteger count = new AtomicInteger() ;/ / counter.// The thread is not safe
public static void add(a){
    count.getAndIncrement(); //count++
}
Copy the code

CAS algorithm

Pessimistic locking

  • Locking is the easiest way to do concurrency, but it’s also the most expensive. An exclusive lock is onePessimistic locking.synchronizedIs a kind ofAn exclusive lock, which assumes the worst case scenario and is executed only if it ensures that other threads do not interfere, causing all other threads requiring the lock to hang, waiting for the thread holding the lock to release it.

Optimistic locking

  • The so-calledOptimistic lockingThat is, each time an operation is performed without locking, it is assumed that there are no conflicts, and if it fails because of conflicts it is retried until it succeeds. Among themCAS(Comparison and exchange,Compare And Swap) is a well-known lock-free algorithm.

Atomic application scenarios

While the CAS-based thread safety mechanism is good and efficient, it should be noted that not all thread safety can be implemented in this way. It is only effective for small granularity requirements such as counters, otherwise there would be no locks.