Multithreading -- three characteristics of concurrent programming

Posted by Niel Roos on Wed, 09 Feb 2022 08:55:51 +0100

Three characteristics of concurrent programming

1. Visibility: the visibility of shared variables among threads, that is, when one thread changes the value of shared variables, other threads can also see and update the value in their own threads. Shared resources are generally placed in the heap space (main memory). Each thread will copy a copy of the public resources to its own thread (local cache) when using the public resources. When one thread changes the shared resources and writes them back to the heap space, other threads do not know that the shared resources have been modified.
Volatile: use volatile to modify the shared variable (non reference type). When one thread changes the shared variable, other threads will read the changed value of the variable. Using volatile will force all threads to read the running value from the heap memory.
synchronized: ensures visibility and triggers data synchronization between local cache and main memory.
2. Orderliness: in order to improve the execution efficiency, the program will rearrange the instructions of the code when compiling or running. To ensure the final consistency in a single thread, instruction rearrangement will not cause problems, but in the case of multithreading, it may produce undesirable results. Memory modified with volatile cannot be reordered, and the read-write access to volatile modified variables cannot be reordered.
3. Atomicity: one or more operations are either all executed and the execution process will not be interrupted by any factor, or they will not be executed at all. Atomicity is like transactions in a database. They are a team and live and die together. Locking, AtomicXXX atomic operation.

Cache line alignment: 64 bytes of cache line is the basic unit of CPU synchronization. Cache line isolation will be more efficient than pseudo sharing
What is a cache line?
The program runs by the CPU to execute the code in the main memory, but the speed difference between the CPU and the main memory is very large. In order to reduce this gap, the CPU cache is used. Now the cache is widely used in computers, which is divided into level 1 cache, level 2 cache, and some have level 3 cache. Each cache is composed of cache lines, which are stored in cache lines in the cache system. The size of the cache line is an integer power of 2 consecutive bytes, generally 32-256 bytes. The most common cache line size is 64 bytes. When multiple threads modify independent variables, if these variables share the same cache line, they will inadvertently affect each other's performance, which is pseudo sharing.

Programming skills of cache line alignment:

public class CacheLinePadding {
    public static long COUNT = 10_0000_0000L;
    private static class T {
        private long p1, p2, p3, p4, p5, p6, p7;
        public long x = 0L;
        private long p9, p10, p11, p12, p13, p14, p15;
    }
    public static T[] arr = new T[2];
    static {
        arr[0] = new T();
        arr[1] = new T();
    }
    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(2);
        Thread t1 = new Thread(()->{
            for (long i = 0; i < COUNT; i++) {
                arr[0].x = i;
            }
            latch.countDown();
        });
        Thread t2 = new Thread(()->{
            for (long i = 0; i < COUNT; i++) {
                arr[1].x = i;
            }
            latch.countDown();
        });
        final long start = System.nanoTime();
        t1.start();
        t2.start();
        latch.await();
        System.out.println((System.nanoTime() - start)/100_0000);
    }
}

JDK8 introduces @ sun misc. The contained annotation is used to ensure the isolation effect of cache lines. To use this annotation, the limiting parameter: - XX: - restrictcontained must be removed

//Note: when running this applet, you need to add parameters: - XX:-RestrictContended
public class T05_Contended {
    public static long COUNT = 10_0000_0000L;
    private static class T {
        @Contended  //Only 1.8 works, ensuring that x is in a separate line
        public long x = 0L;
    }
    public static T[] arr = new T[2];
    static {
        arr[0] = new T();
        arr[1] = new T();
    }
    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(2);
        Thread t1 = new Thread(()->{
            for (long i = 0; i < COUNT; i++) {
                arr[0].x = i;
            }
            latch.countDown();
        });
        Thread t2 = new Thread(()->{
            for (long i = 0; i < COUNT; i++) {
                arr[1].x = i;
            }
            latch.countDown();
        });
        final long start = System.nanoTime();
        t1.start();
        t2.start();
        latch.await();
        System.out.println((System.nanoTime() - start)/100_0000);
    }
}

Topics: Java Cache