Common locks of JUC

1, AtomicInteger

/**
 * @author Java And algorithm learning: Monday
 */
public class AtomicInteger {

    //No volatile
    public AtomicInteger count = new AtomicInteger(0);

    //No synchronized
    public void m() {
        for (int i = 0; i < 1000; i++) {
            //count++
            count.incrementAndGet();
        }
    }

    public static void main(String[] args) {
        T01_AtomicInteger t = new T01_AtomicInteger();

        List<Thread> threadList = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            threadList.add(new Thread(t::m, "t" + i));
        }

        threadList.forEach((o)->{
            o.start();
        });

        threadList.forEach((o)->{
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println(t.count);
    }

}

1. Underlying implementation

CAS (Compare And Swap/Set) lock free optimization and optimistic lock

cas(V, Expected, NewValue)

if V == E

V = New

otherwise try again or fail

5: V alue to change

Expected: what is the expected current value

NewValue: the new value to set

For example, the value to be changed is 3 (i.e. the value obtained at the beginning is 3). When performing CAS operation, I expect the value to be 3, so I can modify the value; if the value is not equal to my Expected value of 3 when performing CAS, it means that the value has been changed by other threads (if it is changed to 4), I will try again (try again). At this time, I expect this value to be 4. If no other thread modifies this value again during CAS operation, that is, it is equal to my Expected value of 4, I will perform CAS operation again and modify the value to a new value.

(1) If another thread changes this value to another value immediately after judging whether the value is my expected value during CAS operation, isn't there still a problem?

CAS operations are supported by CPU primitives, that is, CAS operations are CPU instruction level operations and cannot be changed.

(2) ABA problem

(just like when your girlfriend gets back together with you and makes another boyfriend, she may have changed and no longer be your original girlfriend)

When I execute CAS operation, this value is changed to 2 by other threads, and then to 3, that is, it has been changed in the middle. If it is a foundation type, it can be ignored; If you want to process, you need to add a version number, that is, any modification of this value will add 1 to the version number, which will be checked together with the version number later.

(3) How does the bottom layer of CAS do it?

It's through sun misc. Unsafe class (most methods are native). The main functions of this class are: direct operation of JVM memory (native allocateMemory), direct generation of class instance (native allocateInstance), direct operation of variables (native getInt, native getObject), and CAS related operations (native compareAndSwapObject). This class can only be used by getting the object of this class through reflection or getUnsafe (singleton). It cannot be used directly.

2,SyncLong VS AtomicLong VS LongAdder

/**
 * @author Java And algorithm learning: Monday
 */
public class AtomicSynclongLongAdder {
    private static AtomicLong count1 = new AtomicLong(0L);
    private static long count2 = 0L;
    private static LongAdder count3 = new LongAdder();

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[1000];

        //AtomicLong
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100000; j++) {
                    count1.incrementAndGet();
                }
            });
        }
        long start = System.currentTimeMillis();
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        long end = System.currentTimeMillis();
        System.out.println("AtomicLong: " + count1.get() + " time: " + (end - start));

        //long
        Object o = new Object();
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100000; j++) {
                    synchronized (o) {
                        count2++;
                    }
                }
            });
        }
        start = System.currentTimeMillis();
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        end = System.currentTimeMillis();
        System.out.println("Long: " + count2 + " time: " + (end - start));

        //LongAdder
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100000; j++) {
                    count3.increment();
                }
            });
        }
        start = System.currentTimeMillis();
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        end = System.currentTimeMillis();
        System.out.println("LongAdder: " + count3.longValue() + " time: " + (end - start));
    }

}

LongAdder uses segment lock internally (also implemented by CAS). It will put values into an array. For example, if the array size is 4, 250 threads will be locked in each array, each array will perform operations, and finally sum all array results. Therefore, LongAdder has obvious advantages in ultra-high concurrency.

2, ReentrantLock

1. Reentrantlock can replace synchronized. Reentrantlock can complete the same function as synchronized, but the lock must be released manually. Using synchronized locking, if an exception is encountered, the jvm will automatically release the lock, but reentrantlock must release the lock manually, so the lock is often released in finally.

2. Using reentrantlock, you can try to lock. In this way, it cannot be locked or locked within a specified time. The thread can decide whether to continue to wait.

3. reentrantlock can be specified as a fair lock. ReentrantLock lock=new ReentrantLock(true); Indicates that new is a fair lock. The default is a non fair lock.

1. Fair lock and unfair lock

Fair lock: if a new thread comes, first judge whether there is a waiting thread in the waiting queue of the lock. If there is, it enters the waiting queue and runs first. If there is no, it directly grabs the lock. Such a lock is a fair lock. first come , first served.

Unfair lock: if a new thread directly grabs the lock without judging whether there are threads waiting in the waiting queue, this is an unfair lock. synchronized are all unfair locks.

Whether the new thread checks the queue is the key to fair lock and unfair lock.

2,reentrantlock VS synchronized

1. reentrantlock can replace synchronized

2. reentrantlock must be closed manually. The JVM will automatically release the lock when the synchronized execution ends or exceptions occur

3. reentrantlock is implemented through CAS. synchronized is essentially a lock upgrade

4. reentrantlock can attempt to lock through tryLock

5. reentrantlock can switch between fair locks and non fair locks. synchronized locks are all non fair locks

3, CountDownLatch

/**
 * @author Java And algorithm learning: Monday
 */
public class TestCountDownLatch {

    public static void usingCountDownLatch() {
        Thread[] threads = new Thread[10];
        CountDownLatch countDownLatch = new CountDownLatch(threads.length);

        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                int count = 0;
                for (int j = 0; j < 10000; j++) {
                    count++;
                }
                System.out.println(count);
                countDownLatch.countDown();
            });
        }

        for (Thread thread : threads) {
            thread.start();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("CountDownLatch end...");
    }

    public static void main(String[] args) {
        usingCountDownLatch();
    }

}

The initial size of countDownLatch is defined as 10. For each additional thread Latch value minus 1 (countDownLatch.countDown()), wait (countDownLatch.await()) until the latch value is 0.

4, CyclicBarrier

/**
 * @author Java And algorithm learning: Monday
 */
public class TestCyclicBarrier {

    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(20, ()->{
            System.out.println("Full, departure");
        });

        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                try {
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

}

The size of CyclicBarrier is defined as 20. It is executed only once when 20 threads are reached, otherwise it will wait all the time (barrier.await()). That is, the above program will output 5 times full and departure.

5, Phaser

/**
 * @author Java And algorithm learning: Monday
 */
public class TestPhaser {
    private static MarriagePhaser marriagePhaser = new MarriagePhaser();

    public static void sleep() {
        try {
            TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MarriagePhaser extends Phaser {
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {
            switch (phase) {
                case 0:
                    System.out.println("Everyone here " + registeredParties);
                    System.out.println();
                    return false;
                case 1:
                    System.out.println("Everyone finished " + registeredParties);
                    System.out.println();
                    return false;
                case 2:
                    System.out.println("Everybody leave " + registeredParties);
                    System.out.println();
                    return false;
                case 3:
                    System.out.println("Bridegroom and bride hug " + registeredParties);
                    return true;
                default:
                    return true;
            }
        }
    }

    static class Person implements Runnable {
        String name;

        public Person(String name) {
            this.name = name;
        }

        private void arrive() {
            sleep();
            System.out.println(name + " Arrive at the scene");
            marriagePhaser.arriveAndAwaitAdvance();
        }

        private void eat() {
            sleep();
            System.out.println(name + " eat");
            marriagePhaser.arriveAndAwaitAdvance();
        }

        private void leave() {
            sleep();
            System.out.println(name + " leave");
            marriagePhaser.arriveAndAwaitAdvance();
        }

        private void hug() {
            if ("groom".equals(name) || "bride".equals(name)) {
                sleep();
                System.out.println(name + " embrace");
                marriagePhaser.arriveAndAwaitAdvance();
            } else {
                marriagePhaser.arriveAndDeregister();
            }
        }

        @Override
        public void run() {
            arrive();
            eat();
            leave();
            hug();
        }
    }

    public static void main(String[] args) {
        marriagePhaser.bulkRegister(7);

        for (int i = 0; i < 5; i++) {
            new Thread(new Person("person" + i)).start();
        }

        new Thread(new Person("groom")).start();
        new Thread(new Person("bride")).start();
    }

}

Similar to the fence group, a phased fence. All threads will not execute the next operation until they reach a fence.

6, ReadWriteLock

Read is a shared lock

Write is an exclusive lock (mutex lock)

At first, if the read thread gets the lock, when the second thread is the read thread, it can read together; When the second thread is a write thread, it is blocked and you are not allowed to write. I will write it after I read it.

At first, if the write thread gets the lock, no matter whether the second thread is reading or writing, it will be blocked. I must wait until I change other threads to read and write.

/**
 * @author Java And algorithm learning: Monday
 */
public class TestReadWriteLock {
    private static int value;

    private static ReentrantLock lock = new ReentrantLock();

    private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readWriteLock.readLock();
    private static Lock writeLock = readWriteLock.writeLock();

    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read...");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            value = v;
            Thread.sleep(1000);
            System.out.println("write..." + value);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //8 read threads
        Thread[] tRead = new Thread[8];
        for (int i = 0; i < 8; i++) {
            tRead[i] = new Thread(() -> {
                read(readLock);
            });
        }
        long start = System.currentTimeMillis();
        for (Thread t : tRead) {
            t.start();
        }
        for (Thread t : tRead) {
            t.join();
        }

        //2 write threads
        Thread[] tWrite = new Thread[2];
        for (int i = 0; i < 2; i++) {
            tWrite[i] = new Thread(() -> {
                write(writeLock, new Random().nextInt(10));
            });
        }
        for (Thread t : tWrite) {
            t.start();
        }
        for (Thread t : tWrite) {
            t.join();
        }

        long end = System.currentTimeMillis();
        System.out.println("total time: " + (end - start));
    }

}

If Reentrantlock is used for locking, the program runs for 10s, that is, Reentrantlock reads and writes are exclusive locks;

If ReadWriteLock is used for locking, the program will run for 3s, that is, the read of ReadWriteLock is a shared lock and the write is an exclusive lock.

7, Semaphore (signal light)

Initially, Semaphore is given a default value, which indicates the maximum number of threads allowed to run at the same time. It means current limiting.
Semaphore is a non fair lock by default. When new Semaphore(1, true) new object is set to true, it means it is a fair lock.

/**
 * @author Java And algorithm learning: Monday
 */
public class TestSemaphore {
    public static void main(String[] args) {
        Semaphore s = new Semaphore(1);

        new Thread(()->{
            try {
                s.acquire();
                System.out.println("t1...");
                TimeUnit.SECONDS.sleep(1);
                System.out.println("t1...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }, "t1").start();

        new Thread(()->{
            try {
                s.acquire();
                System.out.println("t2...");
                TimeUnit.SECONDS.sleep(1);
                System.out.println("t2...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }, "t2").start();
    }

}

8, Exchange

/**
 * @author Java And algorithm learning: Monday
 */
public class TestExchanger {

    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();

        new Thread(() -> {
            String s = "T1";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);
        }, "t1").start();

        new Thread(() -> {
            String s = "T2";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);
        }, "t2").start();
    }

}

The exchange() method is blocked. If a thread calls the exchange() method, it will block until the second thread calls the exchange() method; Then the first thread will execute down.

Keywords: Java Back-end JUC lock

Added by pt4siek on Wed, 15 Dec 2021 14:08:25 +0200