Java Concurrent Programming (5) Thread Synchronization [CAS|Atomic Class|Synchronization Container Class|Synchronization Tool Class]

CAS

Overview

The full name of CAS is Compare-And-Swap. It is an atomic instruction of the CPU and is hardware support for concurrent operations on shared data. Its function is that the CPU compares whether two values ​​are equal at a certain moment

Core principle: During operation, CAS first compares whether the value in the main memory and the value in the working memory of the thread are equal. If they are equal, the value in the main memory will be updated to the new value. If they are not equal, they will not be exchanged (if they are not equal ) will always try to update the value through spin)

The CAS instruction has the following problems:

  • ABA problem : There will be an ABA problem when comparing values ​​at two moments. It was originally A, changed to B in the middle, and then changed back to A. The CAS detection thinks that the value has not changed, but in fact it has changed.
    • Solution: AtomicStampedReference in JDK1.5 can be used to solve the ABA problem. The basic idea is to increase the version number and check whether the modified current value and the expired value are consistent. AtomicStampedReference performs CAS operations based on whether the current reference and the expired reference are equal.
  • Long cycle time and high overhead : if CAS fails, it will always try by spinning. If CAS has been unsuccessful for a long time, it may cause a lot of overhead to the CPU. CASTherefore, it is not recommended to use primitives to handle scenarios where conflicts are too frequent ( CASit is an optimistic locking mechanism. Optimistic locking is not suitable for scenarios where conflicts are frequent. In this case, you can choose the pessimistic locking mechanism).

CAS algorithm example

package com.bierce;

public class TestCAS {
    public static void main(String[] args) {
        final CAS cas = new CAS();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                int expectedValue = cas.getValue();//每次更新前必须获取内存中最新的值,因为可能与compareAndSwap中第一次获取的Vaule不一样
                boolean updateRes = cas.compareAndSet(expectedValue, (int) Math.random() * 101);
                System.out.print(updateRes + " "); // true true true true true true true true true true
            }).start();
        }
    }
}
class CAS{
    private int value;
    //获取内存值
    public synchronized  int getValue(){
        return value;
    }
    //比较内存值
    public synchronized  int compareAndSwap(int expectedValue, int newValue){
        int oldValue = value;//
        if (oldValue == expectedValue){
            this.value = newValue;
        }
        return oldValue;
    }
    //设置内存值
    public synchronized  boolean compareAndSet(int expectedValue, int newValue){
        return expectedValue == compareAndSwap(expectedValue, newValue);
    }
}

Atomic class

Overview

In a multi-threaded environment, we can ensure the consistency of data operations through locking ( synchronized/Lock ), but the efficiency is too cumbersome. Therefore, Java provides java.util.concurrent.atomicpackages, which are encapsulated classes for atomic operations, which are convenient to use and high-performance and efficient. The Atomic class method does not have any locking. The underlying core is to ensure thread visibility through volatile and the CAS algorithm operation of the Unsafe class to ensure the atomicity of data operations.

Classification

Atomic classes under the java.util.concurrent.atomic package

AtomicInteger practice

package com.bierce;
import java.util.concurrent.atomic.AtomicInteger;
public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo atomicDemo = new AtomicDemo();

        //模拟多线程,通过原子类保证变量的原子性
        for(int i =0; i < 10; i++){
            new Thread(atomicDemo).start();
        }
    }
}
class AtomicDemo implements Runnable{
    //AtomicInteger原子类提供很多API,根据需求使用
    private AtomicInteger sn = new AtomicInteger(); 
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //getAndIncrement() :类似于 i++
        //incrementAndGet() :类似于 ++i
        //System.out.print(sn.getAndIncrement() + " "); // 输出:1 9 8 7 0 5 2 6 4 3
        System.out.print(sn.incrementAndGet() + " "); // 输出:1 10 9 5 2 8 3 6 7 4
    }
}

Synchronous container class

  • ConcurrentHashMap : Concurrent implementation of synchronized HashMap
  • ConcurrentSkipListMap: Concurrent implementation of synchronized TreeMap
  • CopyOnWriteArrayList: Suitable for concurrent reading or when the collection data traversal requirements are much greater than the list update requirements
  • ThreadLocal: Use the idea of ​​​​exchanging space for time, copy a copy for each thread, and achieve thread safety by isolating threads
  • BlockingQueue
  • ConcurrentLinkedQueue: Ensure thread safety through CAS, adopt delayed update strategy, and improve throughput

ConcurrentHashMap

ConcurrentHashMap internally adopts the idea of ​​"lock segmentation" to replace the exclusive lock of HashTable, adds synchronization operations to control concurrency, and improves performance, but the underlying implementation is different in JDK1.7&&JDK1.8 versions

  • JDK1.7 version: The bottom layer is ReentrantLock+Segment+HashEntry, JDK1.8 version: The bottom layer is synchronized+CAS+HashEntry+red-black tree
  • The lock granularity is reduced in JDK1.8: the lock granularity of JDK1.7 version is based on Segment and contains multiple HashEntry, while the lock granularity of JDK1.8 is HashEntry (first node)
  • The data structure in JDK1.8 is simpler: JDK1.8 uses synchronized for synchronization, so there is no need for a data structure like Segment.
  • JDK1.8 uses red-black trees to optimize linked lists: traversal based on a long linked list is a very long process, but the traversal efficiency of red-black trees is very fast.
  • Extension: Why use synchronized instead of reentrantLock
    • Because the granularity is reduced, synchronized is no worse than ReentrantLock; in coarse-grained locking, ReentrantLock may use Condition to control the boundaries of each low-granularity and is more flexible, but in low-granularity, the advantage of Condition is gone.
    • The JVM development team has never given up on synchronized, and the JVM-based synchronized optimization space is larger. Using embedded keywords is more natural than using APIs.
    • Under a large amount of data operations, API-based ReentrantLock will consume more memory due to JVM memory pressure.

CopyOnWriteArrayList practice

package com.bierce;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * CopyOnWriteArrayList : 支持并发读取时写入
 * 注意:添加数据操作多时,效率比较低,因为每次添加都会进行复制一个新的列表,开销大;
 *      而并发迭代操作多时效率高
 */
public class TestCopyOnWriteArrayList {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayListDemo copyOnWriteArrayListDemo = new CopyOnWriteArrayListDemo();

        for (int i = 0; i < 10; i++) {
            new Thread(copyOnWriteArrayListDemo).start();
        }
    }
}
class CopyOnWriteArrayListDemo implements  Runnable{
    // 不支持并发修改场景,Exception in thread "Thread-1" java.util.ConcurrentModificationException
    //private static List<String> list = Collections.synchronizedList(new ArrayList<String>());
    public static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    static{
        list.add("贾宝玉");
        list.add("林黛玉");
        list.add("薛宝钗");
    }
    @Override
    public void run() {
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
            list.add("test");
        }
    }
}

Synchronization tools

  • CountDownLatch
  • CyclicBarrier
  • Resource Access Control Semaphore

CountDownLatch(Latch)

Overview

  • CountDownLatch is a synchronization tool class used to coordinate synchronization between multiple threads and implement communication between threads.
  • CountDownLatch is suitable for scenarios where one thread waits for other threads to complete their work before continuing execution.

principle

The bottom layer of CountDownLatch is implemented through a counter, and the initialization value of the counter is the number of threads. Whenever a thread completes its task, the counter value is decremented by 1 accordingly. When the counter reaches 0, it means that all threads have completed their tasks, and then the threads waiting on the lock can continue to execute their tasks.

practice

package com.bierce;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.CountDownLatch;

public class TestCountDownLatchDemo {
    public static void main(String[] args) {
        final CountDownLatch countDownLatch = new CountDownLatch(5);//声明5个线程
        CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo(countDownLatch);

        //long start = System.currentTimeMillis();
        Instant start = Instant.now();//JDK8新特性API
        for (int i = 0; i < 5; i++) { //此处循环次数必须和countDownLatch创建的线程个数一致
            new Thread(countDownLatchDemo).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
        }
        //long end = System.currentTimeMillis();
        Instant end = Instant.now();
        //System.out.println("执行完成耗费时长: " + (end - start));
        System.out.println("执行完成耗费时长: " + Duration.between(end, start).toMillis()); // 110ms
    }
}
class CountDownLatchDemo implements Runnable{
    private CountDownLatch countDownLatch;
    public CountDownLatchDemo(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        synchronized (this){ //加锁保证并发安全
            try {
                for (int i = 0; i < 10000; i++) {
                    if (i % 2 == 0) {
                        System.out.println(i);
                    }
                }
            }finally {
                countDownLatch.countDown(); //线程完成后执行减一
            }
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_34020761/article/details/132245992