π’π’π’π£π£π£
Hello! Hello, I'm [Bug terminator], a high-quality creator in CSDNJava field] ποΌ Alibaba cloud invited expert Blogger ποΌ 51CTO TOP celebrity π .
A highly motivated [Java domain blogger] with strong learning ability πππ
π
The field of the [Bug terminator] blog is the learning of [back-end technology], and more [back-end technology] and [learning experience] will be continuously updated in the future. Occasionally, I will share some front-end basic knowledge, update actual projects, and develop applications for enterprises!
π
If you have [xiaocute] who are interested in [back-end technology] and [front-end field], please pay attention to [Bug terminator] πππ
β€οΈβ€οΈβ€οΈ Thank you, big and small! β€οΈβ€οΈβ€οΈ
1, Definition of lock
βοΈ Locking mechanism
The so-called lock can be understood as an integer in the memory. It has two states: idle state and locked state.
Through the lock mechanism, it can ensure that only one thread can enter the code of the critical area at a certain time point in the multi-core and multi-threaded environment, so as to ensure the consistency of the operation data in the critical area.
The so-called lock can be understood as an integer in the memory. It has two states: idle state and locked state. When adding a lock, judge whether the lock is idle. If it is idle, change it to the locked state and return success. If it is locked, it returns failed. When unlocking, change the lock status to idle status.
2, Lock and Synchronized
Traditional Synchronized
Add to code blocks or declared methods for thread safety
But it consumes performance
Lock lock
Locking and unlocking
Lock lock implementation class
Lock implements class fair lock and non fair lock
ReentrantLock // The default is a non fair lock. When true is passed in during construction, it means a fair lock //Ctrl + Alt + T place the cursor on the method to call up the shortcut try catch
The difference between Synchronized and Lock
- Both are unfair locks by default, which can improve performance
- Synchronized is a built-in Java keyword, and Lock is a Java class
- Synchronized cannot determine the status of acquiring a lock. Lock can determine whether a lock has been acquired
- Synchronized will automatically release the Lock, but the Lock will not be automatically released. The Lock needs to be manually closed. If it is not released, it will cause deadlock
- Synchronized thread 1 (lock acquisition, blocking), thread 2 (wait, wait...); Lock will not necessarily wait; Lock will try to acquire the lock tryLock
- Synchronized re-entry lock, non interruptible and unfair; Lock can re-enter the lock. You can judge the lock. You can set fair and unfair locks by yourself. Enter the boolean to select the lock
- Synchronized is suitable for locking a small number of code synchronization problems; Lock lock is suitable for locking a large number of synchronization codes
3, Problems caused by multithreading
β Classic case: producer and consumer issues
Producer and consumer problem describes two thread processes, the so-called producer and consumer, which will cause atomicity (data inconsistency) problems during actual operation. The role of producers is to deliver data all the time, while the role of consumers is to consume data all the time. The key to this problem is to ensure that after the producer produces the product, if the consumer does not consume the product, the production will be stopped and the consumer will continue to produce the product after consumption. If the producer does not produce the product and the consumer has consumed the product, the consumer will wait for the producer to produce the product, and the consumer will consume the product after the producer completes production.
Producer and consumer issues of JUC version
Find Condition through Lock
Legacy and JUC versions
JUC realizes producers and consumers
package com.wanshi.productorcustomer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Communication between threads: producer and consumer issues! Wait for wakeup, notify wakeup * Threads execute alternately, and the same variable is operated by the product customer, num = 0 * C: num+1 * P: num-1 * */ public class Productor2 { public static void main(String[] args) { Data2 data = new Data2(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } } //Determine whether to wait, business and notification //Digital resources class Data2 { private int num = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // +1 operation public void increment() throws InterruptedException { try { lock.lock(); while (num != 0) { //wait for condition.await(); } num ++; System.out.println(Thread.currentThread().getName() + " --- >" + num); //Notify other threads, +1 completed condition.signalAll(); } catch (Exception e) { } finally { lock.unlock(); } } //-1 operation public void decrement() throws InterruptedException { try { lock.lock(); while (num == 0) { //wait for condition.await(); } num --; System.out.println(Thread.currentThread().getName() + " --- >" + num); //Notify other threads, -1 completed condition.signalAll(); } catch (Exception e) { } finally { lock.unlock(); } } }
Any new technology will not only cover the original technology, but also have its own unique advantages and supplement the original technology
Condition
4, Read write lock
Definition of read / write lock
Read / write lock refers to two locks, read lock and write lock.
Why are there read-write locks?
- Because the synchronized granularity is too large, it is not suitable for us. The granularity of reentrant locks is also larger than that of read locks (shared locks). We need locks with small granularity.
- In most scenarios, the read does not need to be locked, but the write needs to be locked, because the write coverage and information inconsistency may occur if the write is not locked, and most reads require locks with smaller granularity, which will occupy less resources.
Exclusive lock (write lock) - can only be occupied by one thread at a time
Shared lock (read lock) multiple threads can occupy at the same time
ReadWriteLock read write lock
package com.wanshi.rw; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Exclusive lock (write lock) can only be occupied by one thread at a time * Shared lock (read lock) multiple threads can occupy at the same time * Read write lock * ReadWriteLock * Read read coexistence * Read write cannot coexist * Write write cannot coexist */ public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache = new MyCache(); //write in for (int i = 1; i <= 5; i++) { final int type = i; new Thread(() -> { myCache.put(type+"", type); }, String.valueOf(i)).start(); } for (int i = 1; i <= 5; i++) { final int type = i; new Thread(() -> { myCache.get(type + ""); }, String.valueOf(i)).start(); } } } /** * Custom cache */ class MyCache { private volatile Map<String, Object> map = new HashMap<>(); //Read / write lock: more granular control private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //Save, write, just want another thread to write public void put(String key, Object val) { readWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "write in" + key); map.put(key, val); System.out.println(Thread.currentThread().getName() + "write in OK"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.writeLock().unlock(); } } //Fetch, read, multiple threads can read public void get(String key) { readWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "read"); System.out.println(map.get(key)); System.out.println(Thread.currentThread().getName() + "read OK"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.readLock().unlock(); } } }
Operation effect
5, Common auxiliary classes (mandatory)
βοΈCountDownLatch
package com.wanshi.add; import java.util.concurrent.CountDownLatch; //Counter public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { //The total number is 6. You can only use it when you have to perform a task! CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i <= 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " Go out"); //Quantity -1 countDownLatch.countDown(); }, String.valueOf(i)).start(); } //Wait for the counter to return to 0 before executing downward countDownLatch.await(); System.out.println("Close Door"); } }
Principle:
countDownLatch.countDown(); Quantity -1
countDownLatch.await(); Wait for the counter to return to 0 before proceeding
Every time a thread calls countDown(), the count will be -1. Assuming that the counter becomes 0, the await method will be awakened and continue to execute
β CycliBarrier
package com.wanshi.add; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CycliBarrierDemo { public static void main(String[] args) { // Collect 7 dragon balls to summon the divine dragon //Summon dragon ball thread CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{ System.out.println("Summon DPCA successfully!"); }); for (int i = 1; i <= 7; i++) { final int temp = i; new Thread(() -> { System.out.println(Thread.currentThread().getName() + "collect" + temp + "Dragon Balls"); try { //Wait for 7 threads to complete execution cyclicBarrier.await(); System.out.println("abc"); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
βSemaphore
Semaphore: semaphore
Grab a parking space!
6 cars – 3 parking spaces
package com.wanshi.add; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class SemaphoreDemo { public static void main(String[] args) { //Current limiting Semaphore semaphore = new Semaphore(3); for (int i = 1; i <= 6; i++) { new Thread(() -> { //acquire() gets try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "Grab the parking space"); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName() + "Leave the parking space"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } //release() }, String.valueOf(i)).start(); } } }
semaphore.acquire(); Gain. If it is full, wait until it is released!
semaphore.release(); Release will release the current semaphore by + 1, and then wake up the waiting thread!
Function: mutually exclusive use of multiple shared resources, concurrent flow restriction, and control the maximum number of threads
6, Other common locks
β Fair lock and unfair lock
Fair lock: it's very fair. You can't jump the queue. You must come first
Unfair locks: very unfair. You can jump the queue (all locks are unfair by default to ensure efficiency)
//Lock lock implementation class, default unfair lock public ReentrantLock() { sync = new NonfairSync(); } //Pass in true to change to fair lock public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
β‘ Reentrant lock
A reentrant lock is also called a recursive lock. It refers to a lock that can be called repeatedly and recursively. After the lock is used in the outer layer, the lock can still be used in the inner layer without deadlock. Such a lock is called a reentrant lock. To put it simply, it is always available when calling other synchronized modified methods or code blocks of this class within a synchronized modified method or code block.
ReentrantLock and synchronized in Java are both reentrant locks, which can avoid deadlocks to a certain extent.
Synchronized version reentrant lock
package com.wanshi.lock; //Synchronized version public class Demo01 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { phone.sms(); }, "A").start(); new Thread(() -> { phone.sms(); }, "B").start(); } } class Phone { public synchronized void sms() { System.out.println(Thread.currentThread().getName() + " ---> send message..."); call(); } public synchronized void call() { System.out.println(Thread.currentThread().getName() + " ---> phone...."); } }
Lock version reentrant lock
package com.wanshi.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo02 { public static void main(String[] args) { Phone2 phone = new Phone2(); new Thread(() -> { phone.sms(); }, "A").start(); new Thread(() -> { phone.sms(); }, "B").start(); } } class Phone2 { Lock lock = new ReentrantLock(); //Note: Lock locks must occur in pairs. If they do not occur in pairs, deadlock may occur public void sms() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " ---> send message..."); call(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void call() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " ---> phone...."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
βΏ Spin lock
spinLock: spinLock means that the thread trying to acquire the lock will not block immediately, but will try to acquire the lock in a circular manner. When the thread finds that the lock is occupied, it will continue to judge the lock status in a circular manner until it is acquired. This has the advantage of reducing the overhead of thread context switching, but the disadvantage is that the loop will consume CPU resources.
Spin lock
Self implementation of spin lock
package com.wanshi.lock; import java.util.concurrent.atomic.AtomicReference; public class SpinlockDemo { AtomicReference<Thread> atomicReference = new AtomicReference<>(); //Lock public void myLock() { Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + " ---> myLock"); //It is expected that the updated conversion thread will exit directly after thread A enters, spin after thread B enters, and wait for thread A to finish spinning after thread B finishes spinning. while (!atomicReference.compareAndSet(null, thread)) { } } //Unlock public void myUnLock() { Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + " ---> myUnLock"); atomicReference.compareAndSet(thread, null); } }
Test spin lock
package com.wanshi.lock; import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { SpinlockDemo lock = new SpinlockDemo(); new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.myUnLock(); } }, "A").start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.myUnLock(); } }, "B").start(); } }
βοΈ deadlock
Deadlock: two threads hold their own resources to try to obtain the resources of other threads, but the other threads are not released, resulting in blocking and deadlock
The causes of deadlock mainly include:
- Insufficient system resources
- There is a problem with the sequence of program execution
- Improper allocation of resources, etc
How to eliminate deadlocks:
-
Mutually exclusive condition: a thread can only be called by one process at a time
-
Inalienable conditions: before the resources obtained by the process are used up, they will not be forcibly deprived by other processes, but can only be released by the process resources that have obtained the resources.
-
Request and hold condition: when a process is blocked due to a request for resources, it will hold on to the obtained resources
-
Circular waiting condition: a circular waiting resource relationship is formed between several processes
The above gives four necessary conditions leading to deadlock. As long as the system is locked, at least one of the above four conditions is true. In fact, the establishment of circular waiting implies the establishment of the first three conditions. It seems unnecessary to list these conditions. However, considering these conditions is beneficial to the prevention of deadlock, because you can prevent deadlock by destroying any of the four conditions.
Deadlock cases
A wants to take B, B wants to take a, and they are deadlocked
package com.wanshi.lock; import java.util.concurrent.TimeUnit; public class DeadLockDemo { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; new Thread(new MyThread(lockA, lockB), "A").start(); new Thread(new MyThread(lockB, lockA), "B").start(); } } class MyThread implements Runnable { private String lockA; private String lockB; public MyThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + " lock: " + lockA + " --> get lock" + lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + " lock: " + lockB + " --> get lock" + lockA); } } } }
solve the problem
- Use jps -l to view the process number
- Using jstack process number to view stack information
Deadlock prevention ensures that the system will never enter a deadlock state
We can prevent deadlock by breaking the four necessary conditions for deadlock generation. Since mutual exclusion of resources is an inherent feature of resource use, it cannot be changed.
- Breaking the "inalienable" condition: a process is in a waiting state when it cannot obtain all the resources it needs. During the waiting period, the resources it occupies will be implicitly released and re added to the system's resource list, which can be used by other processes. The waiting process can be restarted and executed only when it regains its original resources and newly applied resources.
- Breaking "request and hold conditions": the first method is static allocation, that is, each process applies for all the resources it needs at the beginning of execution. The second method is dynamic allocation, that is, each process does not occupy system resources when it applies for the resources it needs.
- Destroy the "circular waiting" condition: adopt the orderly allocation of resources. The basic idea is to number all resources in the system in sequence, and use the larger number for those in short supply and scarce. When applying for resources, the sequence of numbers must be followed. Only the process with a smaller number can apply for a process with a larger number
7, Blocking queue
β½ Definition of blocking queue
Blocking idea: when the desired resource cannot be obtained, the thread waits for the resource, blocks, and wakes up when the resource is available
Blocking queue
Class structure
BlokingQueue is not new
Multithreaded concurrent processing, and the thread pool will use the BlokingQueue
Use queue
Blocking queue provided by JDK
The JDK provides seven types of blocking queues. As follows:
- ArrayBlockingQueue: a bounded blocking queue consisting of an array structure.
- LinkedBlockingQueue: a bounded blocking queue composed of a linked list structure.
- PriorityBlockingQueue: an unbounded blocking queue that supports priority sorting
- DelayQueue: an unbounded blocking queue implemented using priority queues
- SynchronousQueue: a blocking queue that does not store elements
- LinkedTransferQueue: an unbounded blocking queue composed of a linked list structure
- LinkedBlockingQueue: a bidirectional blocking queue composed of a linked list structure
β¨οΈ Common blocking queue
mode | Throw exception | With return value, no exception is thrown | Blocking wait | Wait while |
---|---|---|---|---|
add to | add | offer() | put | offer(,) |
remove | remove | pull() | take | pull(,) |
Judge the first of the queue | element | peek | - | - |
ArrayBlockingQueue detailed usage:
package com.wanshi.bq; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { test2(); } /** * Throw exception */ public static void test1() { //Size of queue ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3); System.out.println(arrayBlockingQueue.add("a")); System.out.println(arrayBlockingQueue.add("b")); System.out.println(arrayBlockingQueue.add("c")); //Legalstateexception: queue full throws an exception // System.out.println(arrayBlockingQueue.add("d")); System.out.println(arrayBlockingQueue.remove()); //Get queue header System.out.println(arrayBlockingQueue.element()); System.out.println(arrayBlockingQueue.remove()); System.out.println(arrayBlockingQueue.remove()); // System.out.println(arrayBlockingQueue.remove()); } public static void test2() throws InterruptedException { ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3); arrayBlockingQueue.offer("a"); arrayBlockingQueue.offer("b"); arrayBlockingQueue.offer("c"); arrayBlockingQueue.offer("d"); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); //Get queue header System.out.println(arrayBlockingQueue.peek()); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); } public static void test3() throws InterruptedException { ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3); arrayBlockingQueue.put("a"); arrayBlockingQueue.put("b"); arrayBlockingQueue.put("c"); // arrayBlockingQueue.put("d"); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); } public static void test4() throws InterruptedException { ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3); arrayBlockingQueue.offer("a"); arrayBlockingQueue.offer("b"); arrayBlockingQueue.offer("c"); arrayBlockingQueue.offer("d", 2, TimeUnit.SECONDS); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS)); } }
SynchronousQueue synchronize queue
package com.wanshi.bq; import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; public class SynchronousQueueDemo { public static void main(String[] args) { BlockingQueue<String> blockingDeque = new SynchronousQueue<>(); new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "put 1"); blockingDeque.put("1"); System.out.println(Thread.currentThread().getName() + "put 2"); blockingDeque.put("2"); System.out.println(Thread.currentThread().getName() + "put 3"); blockingDeque.put("3"); } catch (Exception e) { } },"T1").start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "=>" + blockingDeque.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "=>" + blockingDeque.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "=>" + blockingDeque.take()); } catch (InterruptedException e) { e.printStackTrace(); } }, "T2").start(); } }
Operation effect
β΅ Summary
The above is a brief overview of [Bug terminator] on [JUC concurrent programming] about locks and queues. The lock mechanism is very important. Queues are a good tool to improve our performance. The lock mechanism provides good support for our multithreading and solves the thread safety problem. The probability of asking about locks and queues in the interview is very high. Therefore, we should strengthen our study in this area and read them carefully, which is bound to win locks and queues!
If this [article] is helpful to you, I hope I can give a praise to [Bug terminator] ποΌ It is not easy to create. If there are cute kids who are interested in [back-end technology] and [front-end field], they are also welcome to pay attention β€οΈβ€οΈβ€οΈ [Bug terminator] β€οΈβ€οΈβ€οΈοΌ I will bring you great [harvest and surprise] πππ!