Source code of this article: GitHub · click here || GitEE · click here
1, Lock architecture
1. Basic interface introduction
Two widely used basic API s are involved in the Lock related structure: the ReentrantLock class and the Condition interface. The basic relationships are as follows:
Lock interface
One of the root interfaces for locking resources in Java Concurrent Programming, which specifies several basic methods for locking resources.
ReentrantLock class
Implements the reentrant Lock of the Lock interface, that is, if a thread obtains the Lock of the current instance and enters the task method, it can enter the task method again when the thread does not release the Lock. Features: mutual exclusion and exclusivity, that is, only one thread enters the task at the same time.
Condition interface
The Condition interface describes the Condition variables that may be associated with the lock, and provides more powerful functions. For example, in the thread waiting / notification mechanism, Condition can realize multi-channel notification and selective notification.
2. Use cases
Production and consumption pattern
The write thread adds data to the container, and the read thread obtains data from the container. If the container is empty, the read thread waits.
public class LockAPI01 { private static Lock lock = new ReentrantLock() ; private static Condition condition1 = lock.newCondition() ; private static Condition condition2 = lock.newCondition() ; public static void main(String[] args) throws Exception { List<String> dataList = new ArrayList<>() ; ReadList readList = new ReadList(dataList); WriteList writeList = new WriteList(dataList); new Thread(readList).start(); TimeUnit.SECONDS.sleep(2); new Thread(writeList).start(); } // Read data thread static class ReadList implements Runnable { private List<String> dataList ; public ReadList (List<String> dataList){ this.dataList = dataList ; } @Override public void run() { lock.lock(); try { if (dataList.size() != 2){ System.out.println("Read wait..."); condition1.await(); } System.out.println("ReadList WakeUp..."); for (String element:dataList){ System.out.println("ReadList: "+element); } condition2.signalAll(); } catch (InterruptedException e){ e.fillInStackTrace() ; } finally { lock.unlock(); } } } // Write data thread static class WriteList implements Runnable { private List<String> dataList ; public WriteList (List<String> dataList){ this.dataList = dataList ; } @Override public void run() { lock.lock(); try { dataList.add("Java") ; dataList.add("C++") ; condition1.signalAll(); System.out.println("Write over..."); condition2.await(); System.out.println("Write WakeUp..."); } catch (InterruptedException e){ e.fillInStackTrace() ; } finally { lock.unlock(); } } } }
This production and consumption mode is very similar to the ordering scenario in life. The user orders, informs the back kitchen to cook, and informs the delivery after cooking.
Sequential execution mode
Since thread execution can notify each other, it can also realize the sequential execution of threads based on this mechanism. The basic idea is to wake up the next thread based on conditions after the execution of one thread.
public class LockAPI02 { public static void main(String[] args) { PrintInfo printInfo = new PrintInfo() ; ExecutorService service = Executors.newFixedThreadPool(3); service.execute(new PrintA(printInfo)); service.execute(new PrintB(printInfo)); service.execute(new PrintC(printInfo)); } } class PrintA implements Runnable { private PrintInfo printInfo ; public PrintA (PrintInfo printInfo){ this.printInfo = printInfo ; } @Override public void run() { printInfo.printA (); } } class PrintB implements Runnable { private PrintInfo printInfo ; public PrintB (PrintInfo printInfo){ this.printInfo = printInfo ; } @Override public void run() { printInfo.printB (); } } class PrintC implements Runnable { private PrintInfo printInfo ; public PrintC (PrintInfo printInfo){ this.printInfo = printInfo ; } @Override public void run() { printInfo.printC (); } } class PrintInfo { // Control the next executing thread private String info = "A"; private ReentrantLock lock = new ReentrantLock(); // Three threads, three control conditions Condition conditionA = lock.newCondition(); Condition conditionB = lock.newCondition(); Condition conditionC = lock.newCondition(); public void printA (){ try { lock.lock(); while (!info.equals("A")) { conditionA.await(); } System.out.print("A"); info = "B"; conditionB.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB (){ try { lock.lock(); while (!info.equals("B")) { conditionB.await(); } System.out.print("B"); info = "C"; conditionC.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC (){ try { lock.lock(); while (!info.equals("C")) { conditionC.await(); } System.out.print("C"); info = "A"; conditionA.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
This case often appears in multi-threaded interview questions. The basic idea of how to realize the sequential printing of ABC is the thread based waiting notification mechanism. However, there are many ways to implement it, and the above is only one of them.
2, Read / write lock mechanism
1. Basic API introduction
The exclusive feature of reentry lock determines that performance will cause bottlenecks. In order to improve performance, there is another set of read-write lock mechanism in JDK. A shared read lock and an exclusive write lock are maintained in the read / write lock. In the actual development, there are still too many read scenarios, so the read / write lock can improve the concurrency.
There are two basic API s in the read-write lock related structure: the ReadWriteLock interface and the ReentrantReadWriteLock implementation class. The basic relationships are as follows:
ReadWriteLock
Two basic methods are provided: readLock obtains the read mechanism lock and writeLock obtains the write mechanism lock.
ReentrantReadWriteLock
Specific implementation of interface ReadWriteLock. Features: when based on read lock, other threads can perform read operations. When based on write lock, other threads are prohibited from reading and writing operations.
2. Use cases
Read write separation mode
Through the read-write lock mechanism, write and read data to the data container Map respectively, so as to verify the read-write lock mechanism.
public class LockAPI03 { public static void main(String[] args) throws Exception { DataMap dataMap = new DataMap() ; Thread read = new Thread(new GetRun(dataMap)) ; Thread write = new Thread(new PutRun(dataMap)) ; write.start(); Thread.sleep(2000); read.start(); } } class GetRun implements Runnable { private DataMap dataMap ; public GetRun (DataMap dataMap){ this.dataMap = dataMap ; } @Override public void run() { System.out.println("GetRun: "+dataMap.get("myKey")); } } class PutRun implements Runnable { private DataMap dataMap ; public PutRun (DataMap dataMap){ this.dataMap = dataMap ; } @Override public void run() { dataMap.put("myKey","myValue"); } } class DataMap { Map<String,String> dataMap = new HashMap<>() ; ReadWriteLock rwLock = new ReentrantReadWriteLock() ; Lock readLock = rwLock.readLock() ; Lock writeLock = rwLock.writeLock() ; // Read data public String get (String key){ readLock.lock(); try{ return dataMap.get(key) ; } finally { readLock.unlock(); } } // Write data public void put (String key,String value){ writeLock.lock(); try{ dataMap.put(key,value) ; System.out.println("End of write execution..."); Thread.sleep(10000); } catch (Exception e) { System.out.println("Exception..."); } finally { writeLock.unlock(); } } }
Note: when the put method is always in the sleep state, the read method cannot be executed because of the exclusive nature of the write lock.
3, Basic tools
Introduction to LockSupport
LockSupprot defines a set of public static methods that provide the most basic thread blocking and wakeup functions
Yes.
Basic method
Park(): the current thread is blocked, the current thread is interrupted or the unpark method is called, and the park() method returns;
park(Object blocker): the function is the same as that of park(). The Object object is passed in to record the blocking objects that cause thread blocking, so as to facilitate problem troubleshooting;
parkNanos(long nanos): blocks the current thread within the specified time nanos, and the timeout is returned;
unpark(Thread thread): wakes up the thread in the specified blocking state;
Code case
This process is very common on shopping apps. When you give up when you are ready to pay, there will be a payment failure. You can come back to pay at any time within the payment failure period. After the expiration, you need to re select the payment goods.
public class LockAPI04 { public static void main(String[] args) throws Exception { OrderPay orderPay = new OrderPay("UnPaid") ; Thread orderThread = new Thread(orderPay) ; orderThread.start(); Thread.sleep(3000); orderPay.changeState("Pay"); LockSupport.unpark(orderThread); } } class OrderPay implements Runnable { // Payment status private String orderState ; public OrderPay (String orderState){ this.orderState = orderState ; } public synchronized void changeState (String orderState){ this.orderState = orderState ; } @Override public void run() { if (orderState.equals("UnPaid")){ System.out.println("Order to be paid..."+orderState); LockSupport.park(orderState); } System.out.println("orderState="+orderState); System.out.println("Order preparation for shipment..."); } }
Here, the thread status is controlled based on park and unpark in LockSupport, and the waiting notification mechanism is implemented.
4, Source code address
GitHub·address https://github.com/cicadasmile/java-base-parent GitEE·address https://gitee.com/cicadasmile/java-base-parent
Recommended article: Concurrent Programming Series