1. Lock API
1. Locks package class hierarchy
2. Lock interface
Method Signature | describe |
---|---|
void lock(); | Acquire locks (endless) |
boolean tryLock(); | Acquire locks (just try it) |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; | Acquire locks (out of date) |
void lockInterruptibly() throws InterruptedException; | Acquire locks (at the mercy of others) |
void unlock(); | Release lock |
Condition newCondition(); |
-
Conclusion:
- lock() is most commonly used and threads are not interrupted;
- The lockInterruptibly() method is generally more expensive, and some impls may not implement lockInterruptibly(), but only if an effect interrupt is really needed. Check the description of impl before using it.
public class Demo1_GetLock { // fair lock // static Lock lock = new ReentrantLock(true); // Unfair Lock static Lock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { // Main thread gets lock lock.lock(); Thread th = new Thread(() -> { /* // Subthreads acquire locks (just try it) boolean result = lock.tryLock(); System.out.println("Acquire lock: "+ result"; */ /* // Subthreads acquire locks (out of date) try { System.out.println("Start acquiring lock "; boolean result = lock.tryLock(5, TimeUnit.SECONDS); System.out.println("Acquire lock: "+ result"; } catch (InterruptedException e) { e.printStackTrace(); } */ /* // Subthreads acquire locks (at their disposal) try { System.out.println("Start acquiring lock Interruptibly "; lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("The parent thread stopped me."); } */ // Subthreads acquire locks (endless) System.out.println("Start acquiring locks"); lock.lock(); System.out.println("Lock acquired successfully"); }); th.start(); Thread.sleep(7000); th.interrupt(); System.out.println("th interrupted ..."); Thread.sleep(15000); lock.unlock(); } }
3. Condition
- The wait(), notify(), notifyAll() in Object can only be used with synchronized and can wake up one or all (a single wait set);
- Condition s need to work with Lock to provide multiple waiting sets for more precise control.
Collaboration methods | Deadlock mode 1 | Deadlock Mode 2 (Wake Up, Suspend) | Remarks |
---|---|---|---|
suspend/resume | deadlock | deadlock | Discard |
wait/notify | Undeadlocked | deadlock | Used only for synchronized keywords |
park/unpark | deadlock | Undeadlocked | |
condition | Undeadlocked | deadlock |
-
Simulate condition deadlock
public class Demo3_Condition { private static Lock lock = new ReentrantLock(); private static Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { Thread th = new Thread(() -> { try { // The simulation calls signal first, then await, and it is deadlocked Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); try { System.out.println("here I am ... 1"); condition.await(); System.out.println("here I am ... 2"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }); th.start(); Thread.sleep(5000); lock.lock(); condition.signal(); lock.unlock(); } }
-
Classic example: Blocking a queue through a condition
public class Demo4_Condition3 { public static void main(String[] args) throws InterruptedException { MyBlockingQueue bb = new MyBlockingQueue(5); new Thread(() -> { for (int i = 0; i < 20; i ++) { bb.put("x" + i); } }).start(); Thread.sleep(3000); System.out.println("Start extracting elements from the queue..."); for (int i = 0; i < 10; i ++) { bb.take(); Thread.sleep(3000); } } } /** * Implement a blocking queue of your own and store only n elements * put When the queue is not full, put it directly * If the queue is full, it will block until there is more space * get When there are elements in the queue, get the elements * If there are no elements, wait for them */ class MyBlockingQueue { List<Object> list = new ArrayList<>(); private int length; private Lock lock = new ReentrantLock(); private Condition putCondition = lock.newCondition(); private Condition takeCondition = lock.newCondition(); public MyBlockingQueue(int length) { this.length = length; } public void put(Object obj) { lock.lock(); try { while (true) { if (list.size() < this.length) { list.add(obj); takeCondition.signal(); System.out.println("put: " + obj); return; } else { putCondition.await(); } } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public Object take() { lock.lock(); try { while (true) { if (list.size() > 0) { Object obj = list.remove(0); putCondition.signal(); System.out.println("tack: " + obj); return obj; } else { takeCondition.await(); } } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); return null; } } }
4. ReentrantLock
public class Demo5_Reentrant { static Lock lock = new ReentrantLock(); // Re-lockable public static void main(String[] args) { System.out.println("get lock 1 ..."); lock.lock(); System.out.println("get lock 2 ..."); lock.lock(); // Print a message that the lock has been acquired again System.out.println("here I am ..."); System.out.println("unlock 1 ..."); lock.unlock(); System.out.println("unlock 2 ..."); lock.unlock(); System.out.println("unlock 3 ..."); lock.unlock(); // Third lock release, error } }
- Implement a ReentrantLock on your own
public class MyReentrantLock implements Lock { // Lock Owner AtomicReference<Thread> owner = new AtomicReference<>(); // count recording the number of bursts AtomicInteger count = new AtomicInteger(0); // Waiting queue private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>(); @Override public boolean tryLock() { int ct = count.get(); // Determine if count is 0, if count!=0, the lock is occupied if (ct != 0) { if (Thread.currentThread() == owner.get()) { // Determine if the current thread is occupying or reentrant if the current thread is occupying count.set(ct + 1); return true; } } else { // If count=0, the lock is not occupied, use CAS to lock if (count.compareAndSet(ct, ct + 1)) { owner.set(Thread.currentThread()); return true; } } return false; } @Override public void lock() { if (! tryLock()) { // No locks, join the waiting queue waiters.offer(Thread.currentThread()); LockSupport.park(); while (true) { // If the thread is the head of the queue, try locking Thread head = waiters.peek(); if (head == Thread.currentThread()) { if (!tryLock()) { LockSupport.park(); } else { waiters.poll(); return; } } else { // Thread Suspend LockSupport.park(); } } } } public boolean tryUnlock() { if (owner.get() != Thread.currentThread()) { throw new IllegalMonitorStateException(); } else { // unlock is count-1 int ct = count.get(); int nextC = ct - 1; count.set(nextC); // Determines if count is 0, if 0, the lock is released successfully, otherwise the release fails if (nextC == 0) { owner.compareAndSet(Thread.currentThread(), null); return true; } else { return false; } } } @Override public void unlock() { if (tryUnlock()) { Thread head = waiters.peek(); if (head != null) { LockSupport.unpark(head); } } } @Override public void lockInterruptibly() throws InterruptedException { } public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } }
5. synchronized vs Lock
-
Synchronized
- Advantage
- Easy to use, clear semantics, where to point and where to go.
- Provided by the JVM, various optimization schemes (lock coarsening, lock elimination, biased lock, lightweight lock) are provided.
- The release of locks is done by virtual machines without manual intervention and reduces the likelihood of deadlocks.
- shortcoming
- Some lock advanced functions such as fair lock, interrupt lock, timeout lock, read-write lock, shared lock, etc. cannot be implemented.
- Advantage
-
Lock
- Advantage
- All the shortcomings of synchronized.
- More functionality can be achieved to make synchronized more vulnerable.
- shortcoming
- The unlock needs to be released manually, and improper use by novices can cause deadlocks.
- Advantage
2. ReadWriteLock
1. Concepts
- Maintains a pair of associated locks, one for read-only operations and one for write-only operations.
- Read locks can be held simultaneously by multiple threads, write locks are exclusive, and at the same time, two locks cannot be held by different threads.
2. Demonstration of Lock's drawbacks in the case of more reading and less writing
public class Demo6_ReadWrite01 { volatile long i = 0; Lock lock = new ReentrantLock(); public void read() { lock.lock(); long a = i; lock.unlock(); } public void write() { lock.lock(); i ++; lock.unlock(); } public static void main(String[] args) { final Demo6_ReadWrite01 demo = new Demo6_ReadWrite01(); for (int i = 0; i <= 30; i ++) { int n = i; new Thread(() -> { long startTime = System.currentTimeMillis(); for (int j = 0; j < 400000; j ++) { if (n == 0) demo.write(); else demo.read(); } }).start(); } long endTime = System.currentTimeMillis(); } }
-
At this point, only the first time is a write operation, the other is a read operation.With Lock, only one thread can read at a time, which can be very inefficient.
-
The following is a demonstration of ReadWriteLock
public class Demo6_ReadWrite02 { volatile long i = 0; ReadWriteLock rwLock = new ReentrantReadWriteLock(); public void read() { rwLock.readLock().lock(); long a = i; rwLock.readLock().unlock(); } public void write() { rwLock.writeLock().lock(); i ++; rwLock.writeLock().unlock(); } public static void main(String[] args) { final Demo6_ReadWrite02 demo = new Demo6_ReadWrite02(); for (int i = 0; i <= 30; i ++) { int n = i; new Thread(() -> { long startTime = System.currentTimeMillis(); for (int j = 0; j < 400000; j ++) { if (n == 0) demo.write(); else demo.read(); } }).start(); } long endTime = System.currentTimeMillis(); } }
3. Scenarios applicable
-
Suitable for scenarios where there are more read operations than write operations to improve mutex performance.Examples include security modifications for concurrent threads of collections, caching components.
-
Turn HashMap into thread-safe
// Turn hashmap into a concurrently secure // This is an example given in the ReentrantReadWriteLock comment public class Demo7_Map { private final Map<String, Object> m = new HashMap<>(); private final ReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); public Object get(String key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } public Object put(String key, Object value) { w.lock(); try { return m.put(key, value); } finally { w.unlock(); } } public void clear() { w.lock(); try { m.clear(); } finally { w.unlock(); } } }
4. Lock Degradation
- Refers to the downgrade of a write lock to a read lock.The process of acquiring a read lock while holding a write lock and then releasing the write lock.
- Write locks are thread exclusive and read locks are shared, so write->read is downgraded.(Read->Write is not possible)
public class Demo8_CacheData { public static void main(String[] args) { } } class TeacherInfoCache { static volatile boolean cacheValid; static final ReadWriteLock rwl = new ReentrantReadWriteLock(); /** * Implement a way to use Cache data * Get data directly if the cache is available * Load data from DB to cache if cache is not available * Once you get the data, you need to use it, and it won't change during this time */ static void processCacheData(String dataKey) { Object data = null; rwl.readLock().lock(); try { if (cacheValid) { data = Redis.data.get(dataKey); } else { // Query the database for data // DataBase.queryUserInfo(); //Cache avalanche problem rwl.readLock().lock(); rwl.writeLock().lock(); try { if (! cacheValid) { data = DataBase.queryUserInfo(); Redis.data.put(dataKey, data); cacheValid = true; } else { data = Redis.data.get(dataKey); } } finally { // Lock Degradation Threads acquire read locks while holding write locks and have both read and write locks rwl.readLock().lock(); rwl.writeLock().unlock(); } } } finally { rwl.readLock().unlock(); } } } class DataBase { static String queryUserInfo() { System.out.println("query data base..."); return "name:Kody,age:40,gender:true"; } } class Redis { static Map<String, Object> data = new HashMap<>(); }
5. Internal Principles of Read-Write Locks
3. Read-write lock implementation
1. Implement a reentrant and read-write lock yourself
- Common Code
public class MyAQS { AtomicInteger readCount = new AtomicInteger(0); AtomicInteger writeCount = new AtomicInteger(0); // Exclusive Lock, Owner AtomicReference<Thread> owner = new AtomicReference<>(); // Waiting queue public volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue<>(); public class WaitNode { int type = 0; // 0 for threads that want to acquire exclusive locks and 1 for threads that want to acquire shares Thread thread = null; int arg = 0; public WaitNode(Thread thread, int type, int age) { this.thread = thread; this.type = type; this.arg = arg; } } public void lock() { int arg = 1; // Attempt to acquire an exclusive lock, if successful, exit the method, if failed... if (! tryLock(arg)) { // Mark as Exclusive WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg); waiters.offer(waitNode); // Enter the waiting queue // Cycle Attempt to Hold Lock while (true) { // If the queue header is the current thread WaitNode head = waiters.peek(); if (head != null && head.thread == Thread.currentThread()) { if (! tryLock(arg)) { // Try to acquire an exclusive lock again LockSupport.park(); // Suspend thread if failed } else { // If successful waiters.poll(); // Remove the current thread from the header of the queue return; // And exit the method } } else { // If it is not a queue header element LockSupport.park(); // Suspend the current thread } } } } // Release exclusive lock public boolean unlock() { int arg = 1; // Attempt to release an exclusive lock, return true successfully, or fail... if (tryUnlock(arg)) { WaitNode head = waiters.peek(); if (head != null) { Thread th = head.thread; LockSupport.unpark(th); // The thread that wakes up the head of the queue } return true; // Return true } return false; } // Acquire shared locks public void lockShared() { int arg = 1; if (tryLockShared(arg) < 0) { // If tryLockShared fails // Queue the current thread WaitNode node = new WaitNode(Thread.currentThread(), 1, arg); waiters.offer(node); // Join the queue while (true) { // If the element at the head of the queue is the current thread WaitNode head = waiters.peek(); if (head != null && head.thread == Thread.currentThread()) { if (tryLockShared(arg) >= 0) { // Attempt to acquire shared lock, successful waiters.poll(); // Remove the current thread from the queue WaitNode next = waiters.peek(); if (next != null && next.type == 1) { // If the next thread is also waiting for a shared lock LockSupport.unpark(next.thread); // Wake it up } return; // Exit method } } else { // If the attempt fails LockSupport.park(); } } } } // Unlock shared locks public boolean unLockShared() { int arg = 1; if (tryUnLockShared(arg)) { // When read count becomes zero, release share succeeds WaitNode next = waiters.peek(); if (next != null) { LockSupport.unpark(next.thread); } return true; } return false; } // Attempt to acquire an exclusive lock public boolean tryLock(int acquires) { throw new UnsupportedOperationException(); } // Attempt to release an exclusive lock public boolean tryUnlock(int releases) { throw new UnsupportedOperationException(); } // Attempt to acquire shared lock public int tryLockShared(int acquires) { throw new UnsupportedOperationException(); } // Attempt to unlock shared lock public boolean tryUnLockShared(int releases) { throw new UnsupportedOperationException(); } }
- Reentry Lock Implementation
public class MyReentrantLock implements Lock { private boolean isFair; public MyReentrantLock(boolean isFair) { this.isFair = isFair; } MyAQS common = new MyAQS() { public boolean tryLock(int acquires) { if (isFair) { return tryFairLock(acquires); } else { return tryNotFairLock(acquires); } } // Unfair Lock public boolean tryNotFairLock(int acquires) { // If read count!= 0, returns false if (readCount.get() != 0) { return false; } int wct = writeCount.get(); // Get the current state of the exclusive lock if (wct == 0) { if (writeCount.compareAndSet(wct, wct + 1)) { // Lock by modifying state owner.set(Thread.currentThread()); // Modify owner directly to current thread after lock is grabbed return true; } } else if (owner.get() == Thread.currentThread()) { writeCount.set(wct + acquires); // Modify count value return true; } return false; } // fair lock public boolean tryFairLock(int acquires) { // If read count!= 0, returns false if (readCount.get() != 0) { return false; } int wct = writeCount.get(); // Get the current state of the exclusive lock if (wct == 0) { WaitNode head = waiters.peek(); if (head != null && head.thread == Thread.currentThread() && writeCount.compareAndSet(wct, wct + 1)) { // Lock by modifying state owner.set(Thread.currentThread()); // Modify owner directly to current thread after lock is grabbed return true; } } else if (owner.get() == Thread.currentThread()) { writeCount.set(wct + acquires); // Modify count value return true; } return false; } // Attempt to release an exclusive lock public boolean tryUnlock(int releases) { // If the current thread does not hold an exclusive lock if (owner.get() != Thread.currentThread()) { throw new IllegalMonitorStateException(); } int wc = writeCount.get(); int nextc = wc - releases; // Calculate Exclusive Lock Remaining Occupancy writeCount.set(nextc); if (nextc == 0) { // Is it fully released owner.compareAndSet(Thread.currentThread(), null); return true; } else { return false; } } }; public void lock() { common.lock(); } @Override public void lockInterruptibly() throws InterruptedException { } public boolean tryLock() { return common.tryLock(1); } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public void unlock() { common.unlock(); } @Override public Condition newCondition() { return null; } }
- Read-write lock implementation
public class MyReadWriteLock implements ReadWriteLock { MyAQS common = new MyAQS() { // Attempt to acquire an exclusive lock public boolean tryLock(int acquires) { // If read count!= 0, returns false if (readCount.get() != 0) { return false; } int wct = writeCount.get(); // Get the current state of the exclusive lock if (wct == 0) { if (writeCount.compareAndSet(wct, wct + 1)) { // Lock by modifying state owner.set(Thread.currentThread()); // Modify owner directly to current thread after lock is grabbed return true; } } else if (owner.get() == Thread.currentThread()) { writeCount.set(wct + acquires); // Modify count value return true; } return false; } // Attempt to release an exclusive lock public boolean tryUnlock(int releases) { // If the current thread does not hold an exclusive lock if (owner.get() != Thread.currentThread()) { throw new IllegalMonitorStateException(); } int wc = writeCount.get(); int nextc = wc - releases; // Calculate Exclusive Lock Remaining Occupancy writeCount.set(nextc); if (nextc == 0) { // Is it fully released owner.compareAndSet(Thread.currentThread(), null); return true; } else { return false; } } // Attempt to acquire shared lock public int tryLockShared(int acquires) { while (true) { if (writeCount.get() != 0 && owner.get() != Thread.currentThread()) { return -1; } int rct = readCount.get(); if (readCount.compareAndSet(rct, rct + acquires)) { return 1; } } } // Attempt to unlock shared lock public boolean tryUnLockShared(int releases) { while (true) { int rc = readCount.get(); int nextc = rc - releases; if (readCount.compareAndSet(rc, nextc)) { return nextc == 0; } } } }; @Override public Lock readLock() { return new Lock() { @Override public void lock() { common.lock(); } @Override public boolean tryLock() { return common.tryLock(1); } @Override public void unlock() { common.unlock(); } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } }; } @Override public Lock writeLock() { return new Lock() { @Override public void lock() { common.lockShared(); } @Override public boolean tryLock() { return common.tryLockShared(1) == 1; } @Override public void unlock() { common.unLockShared(); } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } }; } }
- Reentry Lock Test
public class Test_ReentrantLock { volatile static int i = 0; static MyReentrantLock lc = new MyReentrantLock(true); public static void add() { lc.lock(); i ++; lc.unlock(); } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i ++) { new Thread(() -> { for (int j = 0; j < 10000; j ++) { add(); } System.out.println("done..."); }).start(); } Thread.sleep(6000); System.out.println(i); } }
- Read-Write Lock Test
public class Test_ReadWriteLock { static MyReadWriteLock rwLock = new MyReadWriteLock(); static volatile int i = 0; static void add() { i ++; } public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); for (int a = 0; a < 20000; a ++) { final int n = a; new Thread(() -> { if (n % 5 == 0) { rwLock.writeLock().lock(); add(); rwLock.writeLock().unlock(); } else { rwLock.readLock().lock(); System.out.println("i = " + i); int b = i; rwLock.readLock().unlock(); } }).start(); } while (true) { System.out.println("Current Time-consuming: " + (System.currentTimeMillis() - startTime)); Thread.sleep(1000); System.out.println("i = " + i); } } }
2. AQS(AbstractQueuedSynchronizer) Abstract Queue Synchronizer
- java.util.concurrent.locks.AbstractQueuedSynchronizer
- Provides logic for resource occupancy, release, suspension and awakening of threads.
- Reserved for various try methods to be implemented by the user.
- It can be used in a variety of scenarios where you need to control resource contention.(ReentrantLock/CountDownLatch/Semphore)