AQS Synchronization Component--ReentrantLock and Lock

Keywords: Java jvm JDK

ReentrantLock and Lock

Synchronized and ReentrantLock are different
  • Reentrant: Both are reentrant
  • Lock implementation: Synchronized is implemented by jvm, and ReentrantLock is implemented by jdk.(We can understand that one implementation is at the operating system level and the other is at the user's own level.) The implementation of Synchronized is very difficult to see at the JVM level.ReentrantLock is implemented through JVM and we can view the implementation by reading the JVM source code.
  • Performance differences: Synchronized performance is much worse than ReentrantLock prior to Synchronized optimization, and biased locks are introduced at Synchronized. Lightweight locks, after spin locks, do not differ much.Synchronized is more officially recommended when both are available because it is easier to write, and Synchronized is optimized by using cas technology from ReentrantLock.
  • Functional differences: Convenience makes it obvious that synchronized is more convenient and ReentrantLock outperforms Synchronized in granularity and flexibility.
ReentrantLock unique features
  • ReentrantLock can specify whether a lock is fair or unfair, and Synchronized can only be unfair.(A fair lock means that the thread that waits first gets the lock)
  • ReentrantLock provides a Condition class that groups the formations that wake up to require waking up.synchronized is what wakes one thread at random and all threads at random.
  • ReentrantLock provides a mechanism to interrupt threads waiting for locks, lock.locInterruptibly(), and the ReentrantLock implementation is a spin lock that is called through a loop and locked through a cas mechanism.Better performance because threads are not blocked from entering the kernel
@Slf4j
public class LockExample2 {

    // Total number of requests
    public static int clientTotal = 5000;

    // Number of threads executed concurrently
    public static int threadTotal = 200;

    public static int count = 0;

    private final static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

We first declare a resulting instance using private final static Lock lock = new ReentrantLock(), then use

lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }

Perform lock and unlock operations.

Let's take an example to see how this ReentrantReadWriteLock works.

@Slf4j
public class LockExample3 {

    private final Map<String, Data> map = new TreeMap<>();

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private final Lock readLock = lock.readLock();

    private final Lock writeLock = lock.writeLock();

    public Data get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public Set<String> getAllKeys() {
        readLock.lock();
        try {
            return map.keySet();
        } finally {
            readLock.unlock();
        }
    }

    public Data put(String key, Data value) {
        writeLock.lock();
        try {
            return map.put(key, value);
        } finally {
            readLock.unlock();
        }
    }

    class Data {

    }
}

Declare a ReentrantReadWriteLock by private final ReentrantReadWriteLock = new ReentrantReadWriteLock (), then get private final Lock readLock = lock.readLock() private final Lock writeLock = lock.writeLock() read and write locks, respectively.
We add read locks to read maps and write locks to write maps, but the problem here is that this lock is pessimistic, that is, there must be no read locks when a write lock is executed, and it is very likely that a write lock will never execute when there are especially many reads.

Let's take a look at the official example to learn about StampedLock

import java.util.concurrent.locks.StampedLock;

public class LockExample4 {

    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();

        void move(double deltaX, double deltaY) { // an exclusively locked method
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                sl.unlockWrite(stamp);
            }
        }

        //Here's a look at the optimistic read lock case
        double distanceFromOrigin() { // A read-only method
            long stamp = sl.tryOptimisticRead(); //Get an optimistic read lock
            double currentX = x, currentY = y;  //Read two fields into a local variable
            if (!sl.validate(stamp)) { //Check if there are any other write locks after an optimistic read lock is issued?
                stamp = sl.readLock();  //If not, we get another lock on pessimism
                try {
                    currentX = x; // Read two fields into a local variable
                    currentY = y; // Read two fields into a local variable
                } finally {
                    sl.unlockRead(stamp);
                }
            }
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }

        //Here's a pessimistic read lock case
        void moveIfAtOrigin(double newX, double newY) { // upgrade
            // Could instead start with optimistic, not read mode
            long stamp = sl.readLock();
            try {
                while (x == 0.0 && y == 0.0) { //Loop to check if the current state matches
                    long ws = sl.tryConvertToWriteLock(stamp); //Convert Read Lock to Write Lock
                    if (ws != 0L) { //This is to confirm that the conversion to write lock was successful
                        stamp = ws; //If ticket replacement succeeds
                        x = newX; //Make a state change
                        y = newY;  //Make a state change
                        break;
                    } else { //If conversion to write lock is not successful
                        sl.unlockRead(stamp);  //We explicitly release the read lock
                        stamp = sl.writeLock();  //Explicitly write the lock directly and try again through the loop
                    }
                }
            } finally {
                sl.unlock(stamp); //Release read or write locks
            }
        }
    }
}

Let's change the previous one to StampedLock

@Slf4j
public class LockExample5 {

    // Total number of requests
    public static int clientTotal = 5000;

    // Number of threads executed concurrently
    public static int threadTotal = 200;

    public static int count = 0;

    private final static StampedLock lock = new StampedLock();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        long stamp = lock.writeLock();
        try {
            count++;
        } finally {
            lock.unlock(stamp);
        }
    }
}

The difference between here and before is that

   long stamp = lock.writeLock();
        try {
            count++;
        } finally {
            lock.unlock(stamp);
        }

A value is returned after the lock, which needs to be passed in when the lock is unlocked.

Posted by The Bat on Wed, 15 May 2019 01:36:09 -0700