1. Lock interface
- Locks are tools that control access to shared resources
- Lock and synchronize are the most common locks. They can achieve the purpose of thread safety, but they are quite different in use and function
- Lock does not replace synchronize, but provides advanced functions when synchronize is inappropriate or unable to meet requirements
synchronize is inefficient and not flexible enough to know whether the lock was successfully obtained.
- lock() is the most common way to acquire a lock. Wait if the lock is acquired by another thread
- Lock will not automatically release the lock when it is abnormal, like synchronize
- The best practice is to release the lock in finally to ensure that the lock will be released when an exception is sent
- The lock() method cannot be shortened, which will bring hidden dangers. Once it falls into deadlock, lock() will fall into permanent waiting
- tryLock() is used to attempt to acquire a lock
2. Classification of locks
Classification is from different perspectives. A lock may correspond to multiple types, and a type may correspond to multiple locks
- Do threads lock synchronization resources
- Lock (pessimistic lock)
- Not locked (Le Guan lock)
- Can multiple threads share a lock
- Yes (shared lock)
- No (exclusive lock)
- Whether to queue up in multi-threaded competition
- Queue (fair lock)
- Try to jump the queue first, and wait in line after failing to jump the queue (unfair lock)
- Can the same thread acquire the same lock repeatedly
- Yes (reentrant)
- No (no re-entry lock)
- Is it interruptible
- Yes (interruptible lock)
- No (non interruptible lock)
- And so on
- Spin (spin lock)
- Blocking (non spin lock)
3. Optimistic lock and pessimistic lock
3.1 disadvantages of mutually exclusive synchronization lock
- Blocking and wake-up will have performance consumption and context switching
- It may fall into permanent wait if the thread holding the lock falls into deadlock
3.2. What are optimistic locks and pessimistic locks
Pessimistic lock:
- If I don't lock this resource, others will compete for it, which will lead to wrong results. Therefore, in order to ensure the correctness of the results, each pessimistic lock will lock the data every time I obtain and modify the data, so that others can't access the data, so as to ensure that the data is infallible
- The implementation of pessimistic Lock in java is related to synchronize and Lock
Optimistic lock:
- They think that they will not be disturbed by others when dealing with the operation, so they will not lock the operated object
- If the data is different from what I got at the beginning, it means that other people have changed the data during this period, then I can't continue to update the data just now. I will choose to give up, report an error, retry and other strategies
- The implementation of optimistic lock is generally realized by CAS algorithm
- Typical examples of optimistic locks are atomic classes, concurrency containers, and so on
/** * @Classname PessimismOptimismLock * @Description Optimistic lock and pessimistic lock * @Date 2021/4/19 21:56 * @Created by WangXiong */ public class PessimismOptimismLock { int a ; //Pessimistic lock public synchronized void testMethod(){ a++; } public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(); //Optimistic lock atomicInteger.getAndIncrement(); } }
3.3. Suitable scenarios
Pessimistic lock:
- Critical area has IO operation
- The code of critical area is complex or the number of cycles is large
- The competition in the critical area is very fierce
Optimistic lock:
- It is suitable for scenarios where there are few writes and most of them are read. Without locking, the reading performance can be greatly improved
- git version management tool, database version number
4. Reentrant lock and non reentrant lock
Take ReentrantLock as an example
public class LockDemo { static class Outputer { Lock lock = new ReentrantLock(); //String printing method, printing characters one by one public void output(String name){ int len = name.length(); // lock.lock(); try{ for (int i = 0; i < len; i++) { System.out.print(name.charAt(i)); } System.out.println(""); }finally { // lock.unlock(); } } } private void init(){ final Outputer outputer = new Outputer(); new Thread(new Runnable() { public void run() { while (true){ try { Thread.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); } outputer.output("name of a fictitious monkey with supernatural powers"); } } }).start(); new Thread(new Runnable() { public void run() { while (true){ try { Thread.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); } outputer.output("Elder martial brother"); } } }).start(); } public static void main(String[] args) { new LockDemo().init(); } }
4.1 reentrant
The same thread can acquire the same lock multiple times. This has the advantages of avoiding deadlock and improving encapsulation
public class GetHoldCount { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { System.out.println(lock.getHoldCount()); lock.lock(); System.out.println(lock.getHoldCount()); lock.lock(); System.out.println(lock.getHoldCount()); lock.lock(); System.out.println(lock.getHoldCount()); lock.unlock(); System.out.println(lock.getHoldCount()); lock.unlock(); System.out.println(lock.getHoldCount()); lock.unlock(); System.out.println(lock.getHoldCount()); } }
5. Fair lock and unfair lock
5.1. What is fair and unfair
Fairness refers to the order of installation thread requests to allocate locks; Unfairness means that the order of incomplete installation requests can jump in the queue under certain circumstances.
Setting unfairness is to improve efficiency and avoid the gap period caused by wake-up
ReentrantLock is a non fair lock by default. If you want to set it as a fair lock, you need to pass a parameter
5.2. Fairness and unfairness of code demonstration
public class FairLock { public static void main(String[] args) { PrintQueue printQueue = new PrintQueue(); Thread[] thread = new Thread[10]; for (int i = 0; i < thread.length; i++) { thread[i] = new Thread(new Job(printQueue)); } for (int i = 0; i < thread.length; i++) { thread[i].start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Job implements Runnable{ PrintQueue printQueue; public Job(PrintQueue printQueue) { this.printQueue = printQueue; } public void run() { System.out.println(Thread.currentThread().getName() + "Start printing"); printQueue.printJob(new Object()); System.out.println(Thread.currentThread().getName() + "Print complete"); } } class PrintQueue{ // private Lock queueLock = new ReentrantLock(); private Lock queueLock = new ReentrantLock(true); public void printJob(Object document){ queueLock.lock(); try { int duration = new Random().nextInt(10) + 1; System.out.println(Thread.currentThread().getName() + "Printing, need" + duration); Thread.sleep(duration * 1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } queueLock.lock(); try { int duration = new Random().nextInt(10) + 1; System.out.println(Thread.currentThread().getName() + "Printing, need" + duration); Thread.sleep(duration * 1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } } }
5.3 advantages and disadvantages
Fair lock:
- Advantages: fairness between threads. Each thread always has the opportunity to execute after waiting for a period of time.
- Disadvantages: slower, low throughput
Unfair lock:
- Faster and higher throughput
- Thread starvation may occur, that is, some threads cannot be executed for a long time
6. Shared lock and exclusive lock
Take ReentrantReadWriteLock as an example
- Exclusive locks are called exclusive locks and exclusive locks
- Shared lock, also known as read lock, can be viewed but cannot be modified or deleted after obtaining the shared lock. Other threads can also view but cannot modify or delete the data after obtaining the shared lock
- The typical shared lock and exclusive lock are read-write lock R Mm-hmm. Taoranting ReentrantReadWriteLock, in which the read lock is a shared lock and the write lock is an exclusive lock
- Before there is no read-write lock, let's assume that ReentrantLock is used. Although we ensure thread safety, we also waste some resources: multiple read operations are carried out at the same time, and there is no thread safety problem
- Use the read lock in the read place and the write lock in the write place, which can be controlled flexibly. If there is no write lock, the read is non blocking, which improves the execution efficiency of the program
- One sentence summary: either one or more threads have read locks at the same time, or one thread has write locks, but the two will not occur at the same time (either read more or write one)
Code demonstration
public class CinemaReadWrite { private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); private static void read(){ readLock.lock(); try{ System.out.println(Thread.currentThread().getName() + "Read lock acquired, reading"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }finally { System.out.println(Thread.currentThread().getName() + "Read lock released"); readLock.unlock(); } } private static void write(){ writeLock.lock(); try{ System.out.println(Thread.currentThread().getName() + "Got write lock, writing"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }finally { System.out.println(Thread.currentThread().getName() + "The write lock was released"); writeLock.unlock(); } } public static void main(String[] args) { new Thread(new Runnable() { public void run() { CinemaReadWrite.read(); } }, "Thread1").start(); new Thread(new Runnable() { public void run() { CinemaReadWrite.read(); } }, "Thread2").start(); new Thread(new Runnable() { public void run() { CinemaReadWrite.write(); } }, "Thread3").start(); new Thread(new Runnable() { public void run() { CinemaReadWrite.write(); } }, "Thread4").start(); } }
test
6.1 queue jumping strategy of read-write lock
- Fair lock: queue jumping is not allowed
- Unfair lock:
- Write the lock and jump the queue at any time (if you can't get the lock, queue up)
- Read lock can only jump the queue when waiting for the head node to not want to obtain the write lock (read lock can still jump the queue)
If the head node of the test queue is write, read will not jump the queue
public static void main(String[] args) { new Thread(new Runnable() { public void run() { CinemaReadWrite.write(); } }, "Thread1").start(); new Thread(new Runnable() { public void run() { CinemaReadWrite.read(); } }, "Thread2").start(); new Thread(new Runnable() { public void run() { CinemaReadWrite.read(); } }, "Thread3").start(); new Thread(new Runnable() { public void run() { CinemaReadWrite.write(); } }, "Thread4").start(); new Thread(new Runnable() { public void run() { CinemaReadWrite.read(); } }, "Thread5").start(); }
The head node is a read lock, which can also jump the queue
public class NonfairBargeDemo { //It's not fair. In fact, you can jump the queue private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false); private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); private static void read(){ System.out.println(Thread.currentThread().getName() + "Start trying to acquire read lock"); readLock.lock(); try { System.out.println(Thread.currentThread().getName() + "Get read lock, reading"); Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + "Release read lock"); readLock.unlock(); } } private static void write(){ System.out.println(Thread.currentThread().getName() + "Start trying to acquire write lock"); writeLock.lock(); try { System.out.println(Thread.currentThread().getName() + "Get write lock, writing"); Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + "Release write lock"); writeLock.unlock(); } } public static void main(String[] args) { new Thread(()->write(), "Thread1").start(); new Thread(()->read(), "Thread2").start(); new Thread(()->read(), "Thread3").start(); new Thread(()->write(), "Thread4").start(); new Thread(()->read(), "Thread5").start(); new Thread(new Runnable() { @Override public void run() { Thread thread[] = new Thread[1000]; for (int i = 0; i < thread.length; i++) { thread[i] = new Thread(()->read(), "Created by a child thread Threaad"+i); } for (int i = 0; i < thread.length; i++) { thread[i].start(); } } }).start(); } }
If it's Fair
public class NonfairBargeDemo { //Set to fair, and execute in the order of queuing private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true); private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); private static void read(){ System.out.println(Thread.currentThread().getName() + "Start trying to acquire read lock"); readLock.lock(); try { System.out.println(Thread.currentThread().getName() + "Get read lock, reading"); Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + "Release read lock"); readLock.unlock(); } } private static void write(){ System.out.println(Thread.currentThread().getName() + "Start trying to acquire write lock"); writeLock.lock(); try { System.out.println(Thread.currentThread().getName() + "Get write lock, writing"); Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + "Release write lock"); writeLock.unlock(); } } public static void main(String[] args) { new Thread(()->write(), "Thread1").start(); new Thread(()->read(), "Thread2").start(); new Thread(()->read(), "Thread3").start(); new Thread(()->write(), "Thread4").start(); new Thread(()->read(), "Thread5").start(); new Thread(new Runnable() { @Override public void run() { Thread thread[] = new Thread[1000]; for (int i = 0; i < thread.length; i++) { thread[i] = new Thread(()->read(), "Created by a child thread Threaad"+i); } for (int i = 0; i < thread.length; i++) { thread[i].start(); } } }).start(); } }
6.2 upgrade and downgrade of locks
public class Upgrading { private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true); private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); private static void readUpgrading(){ System.out.println(Thread.currentThread().getName() + "Start trying to acquire read lock"); readLock.lock(); try { System.out.println(Thread.currentThread().getName() + "Get read lock, reading"); Thread.sleep(1000); System.out.println("Upgrades can cause congestion"); writeLock.lock(); System.out.println(Thread.currentThread().getName() + "The write lock is obtained and the upgrade is successful"); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + "Release read lock"); readLock.unlock(); } } private static void writeUpgrading(){ System.out.println(Thread.currentThread().getName() + "Start trying to acquire write lock"); writeLock.lock(); try { System.out.println(Thread.currentThread().getName() + "Get write lock, writing"); Thread.sleep(1000); readLock.lock(); System.out.println("Obtain the read lock directly without releasing the write lock, and demote successfully"); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); System.out.println(Thread.currentThread().getName() + "Release write lock"); writeLock.unlock(); } } public static void main(String[] args) throws InterruptedException { System.out.println("It's OK to demote first"); Thread thread1 = new Thread(()-> writeUpgrading(), "Thread1"); thread1.start(); thread1.join(); System.out.println("-------------------------"); System.out.println("Demo upgrade is not possible"); Thread thread2 = new Thread(()-> readUpgrading(), "Thread2"); thread2.start(); } }
Why not support lock upgrade? Avoid deadlock
6.3 summary
- ReentrantReadWriteLock implements the ReadWriteLock interface. There are two main methods: readLock() and writeLock() to obtain the read lock and write lock
- Either read more or write more
- Queue jumping strategy: in order to prevent hunger, you can't jump the queue after reading the lock
- Promotion / demotion strategy: you can only demote, not upgrade
- ReentrantReadWriteLock is applicable to the situation of more reading and less writing. Reasonable use can improve the concurrency efficiency
7. Spin lock and blocking lock
- Spin lock: continuous requests, attempts to obtain the lock, and does not block when the lock is not obtained
- If the content in the synchronization code block is too simple, the state transition may take longer than the execution time of user code. For this period of time, switching threads is not worth the loss.
- Blocking lock: if the lock is not obtained, it will fall into a blocking state
Spin is actually using do The while loop
//Methods of AtomicInteger atomic class public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
Implement a simple spin lock
public class SpinLock { private AtomicReference<Thread> sign = new AtomicReference<>(); public void loock(){ Thread currentThread = Thread.currentThread(); while (!sign.compareAndSet(null, currentThread)){ System.out.println("Spin acquisition failed, try again"); } } public void unlock(){ Thread currentThread = Thread.currentThread(); sign.compareAndSet(currentThread, null); } public static void main(String[] args) { SpinLock spinLock = new SpinLock(); Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "Attempt to acquire spin lock"); spinLock.loock(); System.out.println(Thread.currentThread().getName() + "Spin lock acquired"); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } finally { spinLock.unlock(); System.out.println(Thread.currentThread().getName() + "Spin lock released"); } } }; new Thread(runnable).start(); new Thread(runnable).start(); } }
It is suitable for situations where concurrency is not particularly high
8. Interruptible lock
In Java, synchronize is not an interruptible Lock, but Lock is an interruptible Lock, because both tryLock(time) and inck interrupt can respond to interrupts
public class LockInterruptibly implements Runnable { private static Lock lock = new ReentrantLock(); public void run() { System.out.println(Thread.currentThread().getName() + "Attempt to acquire lock"); try{ //lockInterruptibly is equivalent to waiting indefinitely. It can be interrupted during locking lock.lockInterruptibly(); try { System.out.println(Thread.currentThread().getName() + "Lock acquired"); Thread.sleep(5000); }catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "Interrupted during sleep"); }finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + "The lock was released"); } }catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "Interrupted during lock waiting"); } } public static void main(String[] args) { LockInterruptibly r1 = new LockInterruptibly(); Thread thread0 = new Thread(r1); Thread thread1 = new Thread(r1); thread0.start(); thread1.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // thread0.interrupt(); thread1.interrupt(); } }
9. Lock optimization
- Reduce synchronization code block
- Try not to lock the method
- Reduce the number of lock requests
- Try not to include the lock in the lock
- Select the appropriate lock or tool