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.