Concurrent programming - management of shared model-3
This article is a video guide 👉 Dark horse programmer - Concurrent Programming
1. Problems caused by sharing
1-1 Critical Section
- There is no problem for a program to run multiple threads
- The problem is that multiple threads access shared resources
- In fact, there is no problem for multiple threads to read shared resources
- Problems occur when multiple threads interleave instructions when reading and writing to shared resources
- If there are multithreaded read-write operations on shared resources in a code block, this code block is called a critical area
For example, the critical zone in the following code:
static int counter = 0; static void increment() // Critical zone { counter++; } static void decrement() // Critical zone { counter--; }
1-2 Race Condition
When multiple threads execute in the critical area, the results cannot be predicted due to different execution sequences of code, which is called race condition
2.synchronized solution
2-1 solution
In order to avoid the competitive conditions in the critical zone, there are many means to achieve the goal.
- Blocking solution: synchronized, Lock
- Non blocking solution: atomic variables
This section uses a blocking solution: synchronized to solve the above problems, commonly known as * * object lock. It uses mutually exclusive * * to make at most one thread hold the object lock at the same time. When other threads want to obtain the object lock, they will be blocked. This ensures that the thread with the lock can safely execute the code in the critical area without worrying about thread context switching
be careful:
Although the synchronized keyword can be used to complete mutual exclusion and synchronization in java, they are different:
- Mutual exclusion is to ensure that the race condition of the critical area occurs. Only one thread can execute the code of the critical area at the same time
- Synchronization is due to the different execution order of threads. One thread needs to wait for other threads to run to a certain point
synchronized actually uses object locks to ensure the atomicity of the code in the critical area. The code in the critical area is inseparable externally and will not be interrupted by thread switching.
2-2 use of synchronized
grammar
synchronized(object) { //Critical zone }
solve
static int counter = 0; //Create a public object as the object of the object lock static final Object room = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 5000; i++) { synchronized (room) { counter++; } } }, "t1"); Thread t2 = new Thread(() -> { for (int i = 0; i < 5000; i++) { synchronized (room) { counter--; } } }, "t2"); t1.start(); t2.start(); t1.join(); t2.join(); log.debug("{}",counter); }
2-3 synchronized on method
-
On the member method, the lock is the current instance object
public class Demo { //Add the synchronized keyword to the method public synchronized void test() { } //Equivalent to public void test() { synchronized(this) { } } }
-
On a static method, the lock is the Class object of the current Class
public class Demo { //Add the synchronized keyword to the static method public synchronized static void test() { } //Equivalent to public void test() { synchronized(Demo.class) { } } }
3. Thread safety analysis of variables
3-1 are member variables and static variables thread safe?
- If they are not shared, they are thread safe
- If they are shared, they can be divided into two cases according to whether their state can be changed
- Thread safe if there are only read operations
- If there are read and write operations, this code is a critical area and thread safety needs to be considered
3-2 are local variables thread safe?
-
Local variables are thread safe
-
However, the object referenced by the local variable may not be (depending on whether the object is shared and read-write operations are performed)
- If the object does not escape the scope of the method, it is thread safe
- If the object escapes from the scope of the method, thread safety needs to be considered
-
Local variables are thread safe - each method creates a stack frame in the stack of the corresponding thread and will not be shared by other threads
-
If the called object is shared and a read-write operation is performed, the thread is not safe
-
If it is a local variable, the corresponding object will be created in the heap, and there will be no thread safety problem
3-3 common thread safety classes
- String
- Integer
- StringBffer
- Random
- Vector (thread safe implementation class of List)
- Hashtable (thread safe implementation class of Hash)
- Classes under java.util.concurrent package
Here, they are thread safe, which means that when multiple threads call a method of their same instance, they are thread safe
For example:
Hashtable table = new Hashtable(); new Thread(()->{ table.put("key", "value1"); }).start(); new Thread(()->{ table.put("key", "value2"); }).start();
- Each of their methods is atomic (synchronized is added)
- However, note that the combination of their multiple methods is not atomic, so thread safety problems may occur
Combination of thread safe class methods
Analyze whether the following code is thread safe?
Hashtable table = new Hashtable(); // Thread 1, thread 2 if( table.get("key") == null) { table.put("key", value); }
It can be that the combination of multiple methods is not atomic, so thread safety problems may occur
Immutable class thread safety
String and Integer are immutable classes. Because their internal state cannot be changed, their methods are thread safe
Some students may have questions. String has methods such as replace and substring that can change the value. How do these methods ensure thread safety?
This is because the return values of these methods create a new object instead of directly changing the String and Integer objects themselves.
3-4 example analysis
Example 1:
public class MyServlet extends HttpServlet { // Is it safe? no Map<String,Object> map = new HashMap<>(); // Is it safe? yes String S1 = "..."; // Is it safe? yes final String S2 = "..."; // Is it safe? no Date D1 = new Date(); // Is it safe? no, the reference remains unchanged, but the year, month and day in it are variable final Date D2 = new Date(); public void doGet(HttpServletRequest request, HttpServletResponse response) { //Use the above variables } }
Example 2:
public class MyServlet extends HttpServlet { // Is it safe? no, shared private UserService userService = new UserServiceImpl(); public void doGet(HttpServletRequest request, HttpServletResponse response) { userService.update(...); } } public class UserServiceImpl implements UserService { // Record the number of calls private int count = 0;//There may be a problem with multiple thread calls here public void update() { // ... count++; } }
Example 3:
@Aspect @Component public class MyAspect {//In a single instance, member variables are shared // Is it safe? no private long start = 0L; @Before("execution(* *(..))") public void before() { start = System.nanoTime(); } @After("execution(* *(..))") public void after() { long end = System.nanoTime(); System.out.println("cost time:" + (end-start)); } }
4.Monitor
4-1 Java object header
Take the 32-bit virtual machine as an example:
Common objects:
Array object:
The Mark Word structure is:
64 bit virtual machine Mark Word:
4-2 principle of Monitor
Monitor is translated as monitor or tube pass
Each Java object can be associated with a Monitor object. If you use synchronized to lock the object (heavyweight), the pointer to the Monitor object will be set in the Mark Word of the object header
The Monitor structure is as follows:
- At the beginning, the Owner in Monitor is null
- When Thread-2 executes synchronized(obj), the Owner of the Monitor will be set to Thread-2. There can only be one Owner in the Monitor
- When Thread-2 is locked, if Thread-3, Thread-4 and Thread-5 also execute synchronized(obj), it will enter the EntryList (BLOCKED)
- Thread-2 executes the contents of the synchronization code block, and then wakes up the waiting threads in the EntryList to compete for the lock. The competition is unfair
- In the figure, Thread-0 and Thread-1 in the WaitSet are threads that have obtained locks before, but the conditions are not met to enter the WAITING state. They will be analyzed later when we talk about wait notify
be careful:
- synchronized must enter the monitor of the same object to have the above effect
- Objects that are not synchronized are not associated with monitors and do not comply with the above rules
5. Advanced synchronized principle
5-1 lightweight lock
- Lightweight lock usage scenario: when an object is accessed by multiple threads, but the access time is staggered (there is no competition), lightweight locks can be used to optimize.
- Lightweight locks are transparent to the user, that is, the syntax is still synchronized
Here's a chestnut: suppose there are two methods to synchronize blocks and lock the same object
static final Object obj = new Object(); public static void method1() { synchronized( obj ) { // Synchronization block A method2(); } } public static void method2() { synchronized( obj ) { // Synchronization block B } }
-
Create a Lock Record object. The stack frame of each thread will contain a Lock Record object. The mark word of the locked object can be stored internally (Monitor is no longer used at the beginning)
-
Let the Object reference in the lock record point to the lock Object, and try to replace the mark word in the Object with cas. Put the mark word into the lock record and save it
-
If cas replacement is successful, the Object header of the Object is replaced with the address and state 00 of the lock record (lightweight lock state), and the thread locks the Object
-
If cas fails, there are two situations:
-
If other threads already hold the lightweight lock of the Object, it indicates that there is competition and enters the lock expansion process
-
If the synchronized lock reentry is performed by yourself, add another Lock Record as the reentry count
-
-
When exiting the synchronized code block (when unlocking), if there is a lock record with a value of null, it indicates that there is reentry. At this time, reset the lock record, indicating that the reentry count is reduced by one
-
When exiting the synchronized code block (when unlocking), the value of the lock record is not null. In this case, cas is used to restore the value of Mark Word to the object header
- If successful, the unlocking is successful
- Failure indicates that the lightweight lock has undergone lock expansion or has been upgraded to a heavyweight lock. Enter the heavyweight lock unlocking process
5-2 lock expansion
If the CAS operation fails when trying to add a lightweight lock, then another thread adds a lightweight lock (with competition) to this object. At this time, lock expansion is required to change the lightweight lock into a heavyweight lock.
-
When Thread-1 performs lightweight locking, Thread-0 has already applied lightweight locking to the object
-
At this time, Thread-1 fails to add a lightweight lock and enters the lock expansion process
- That is, apply for the Monitor lock for the Object object and let the Object point to the heavyweight lock address
- Then enter the EntryList BLOCKED of Monitor
-
When Thread-0 exits the synchronization block unlocking, cas is used to restore the value of Mark Word to the object header, which fails. At this time, the heavyweight unlocking process will enter, that is, find the Monitor object according to the Monitor address, set the Owner to null, and wake up the BLOCKED thread in the EntryList
5-3 spin optimization
When the heavyweight lock competes, spin can also be used for optimization. If the current thread spins successfully (that is, the lock holding thread has exited the synchronization block and released the lock), the current thread can avoid blocking
- Spin will occupy CPU time. Single core CPU spin is a waste, and multi-core CPU spin can give play to its advantages.
- After Java 6, the spin lock is adaptive. For example, if the object has just succeeded in a spin operation, it is considered that the possibility of successful spin this time will be high, so spin more times; On the contrary, less spin or even no spin. In short, it is more intelligent.
- After Java 7, you can't control whether to turn on the spin function
5-4 deflection lock
The lightweight lock still needs to perform CAS operation every time it re enters when there is no competition (just its own thread).
Java 6 introduces bias lock for further optimization: only when CAS is used for the first time to set the thread ID to the Mark Word header of the object, and then it is found that the thread ID is its own, it means that there is no competition and there is no need to re CAS. In the future, as long as there is no competition, the object belongs to the thread
Biased state
- Normal: normal status. No lock is added. The first 62 bits store the information of the object. The last 2 bits are status (01). The penultimate bit indicates whether to use bias lock (unused: 0)
- Biased: biased status. Biased lock is used. The first 54 bits save the ID of the current thread. The last 2 bits are status (01). The penultimate bit indicates whether to use biased lock (use: 1)
- Lightweight: a lightweight lock is used. The first 62 bits hold the pointer of the lock record, and the last two bits are the status (00)
- Heavyweight: the heavyweight lock is used. The first 62 bits store the address pointer of the Monitor, and the last two bits are the status (10)
- If the bias lock is turned on (on by default), the last three digits of Mark Word of the object should be 101 when creating the object
- However, the bias lock is delayed by default and will not take effect as soon as the program starts. Instead, the created object will not be set to the bias state until the program runs for a period of time (a few seconds)
- If the bias lock is not opened, the last three digits of Mark Word of the object should be 001
Revocation bias
The following situations will invalidate the bias lock of the object
- Call the hashCode method of the object
- The object is used by multiple threads
- The wait/notify method is called (calling the wait method will cause lock inflation and use heavyweight locks)
Batch re bias
- If the object is accessed by multiple threads, but there is no competition between threads, the object biased to T1 still has the opportunity to re bias to T2. Re bias resets the Thread ID
- When the revocation exceeds 20 times (exceeding the threshold), the JVM will think whether the bias is wrong. At this time, it will re bias to the locking thread when locking the object
Batch undo
When the unbiased lock threshold is revoked more than 40 times, the jvm will feel that it is really biased wrong and should not be biased at all. Therefore, all objects of the whole class will become non biased, and the new objects will also be non biased
6.Wait/Notify
6-1 principle
- When the Owner thread finds that the conditions are not met, it calls the wait method to enter the WaitSet and change to the WAITING state
- Both BLOCKED and WAITING threads are BLOCKED and do not occupy CPU time slices
- The BLOCKED thread wakes up when the Owner thread releases the lock
- The WAITING thread will wake up when the Owner thread calls notify or notifyAll, but after waking up, it does not mean that the Owner obtains the lock immediately. It still needs to enter the EntryList to compete again
A picture in the art of Java Concurrent Programming is attached here:
6-2 API introduction
- wait() causes the thread entering the object monitor to wait in the waitSet
- notify() selects one of the threads waiting for waitSet on the object to wake up
- notifyAll() wakes up all the threads waiting for waitSet on the object
- The wait() method will release the lock of the object and enter the WaitSet waiting area, so that other threads can get the lock of the object. Unlimited wait until notify
- wait(long n) a time limited wait until n milliseconds, or notify
Note: the wait and notify methods can only be called after the object is locked
example:
@Slf4j public class Test6 { final static Object lock = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (lock) { log.debug("implement..."); try { lock.wait();//Let the thread wait on the lock } catch (InterruptedException e) { e.printStackTrace(); } log.debug("Other codes..."); } }, "t1").start(); new Thread(() -> { synchronized (lock) { log.debug("implement..."); try { lock.wait();//Let the thread wait on the lock } catch (InterruptedException e) { e.printStackTrace(); } log.debug("Other codes..."); } }, "t2").start(); Sleeper.sleep(2); log.debug("awaken lock Thread on"); synchronized (lock) { lock.notify(); //Wake up a thread on lock //lock.notifyAll(); // Wake up all waiting threads on lock } } } //output 20:36:08 DEBUG [t1] (Test6.java:13) - implement... 20:36:08 DEBUG [t2] (Test6.java:25) - implement... 20:36:10 DEBUG [main] (Test6.java:35) - awaken lock Thread on 20:36:10 DEBUG [t1] (Test6.java:19) - Other codes...
6-3 correct use of wait / notify
Before you start, look at the difference between sleep(long n) and wait(long n):
- sleep is the Thread method, while wait is the Object method
- sleep does not need to be used with synchronized forcibly, but wait needs to be used with synchronized
- sleep does not release the object lock while sleeping, but wait releases the object lock while waiting
- Their status is TIMED_WAITING
The following parts are recommended for reference: Notes after reading the art of Java Concurrent Programming - part4
The waiting party shall follow the following principles:
- Get lock of object
- If the condition is not satisfied, call the wait() method of the object, and check the condition after being notified
- If the conditions are met, the corresponding logic is executed
Corresponding pseudo code:
synchronized(object){ while(Conditions not met){ object.wait(); } Corresponding processing logic }
The notifying party shall follow the following principles:
- Get lock of object
- change a condition
- Notifies all threads waiting on the object
Corresponding pseudo code:
synchronized(object){ change a condition object.notifyAll(); }
7.Park/Unpark
7-1 basic use
park/unpark are methods in the LockSupport class
//Pause thread LockSupport.park; //Resume thread operation LockSupport.unpark(thread);
park before unpark:
@Slf4j public class Test24 { public static void main(String[] args) { Thread t1 = new Thread(() -> { log.debug("start..."); Sleeper.sleep(1); log.debug("park..."); //Pause thread LockSupport.park(); log.debug("resume..."); }, "t1"); t1.start(); Sleeper.sleep(2); log.debug("unpark..."); //Resume thread operation LockSupport.unpark(t1); } } 13:11:08 DEBUG [t1] (Test24.java:12) - start... 13:11:09 DEBUG [t1] (Test24.java:14) - park... 13:11:10 DEBUG [main] (Test24.java:20) - unpark... 13:11:10 DEBUG [t1] (Test24.java:16) - resume...
7-2 features
Compared with wait/notify of Object:
- wait, notify and notifyAll must be used together with Object Monitor, but park and unpark do not
- park and unpark block and wake up threads in terms of threads, while notify can wake up only one waiting thread randomly. notifyAll wakes up all waiting threads, which is not so accurate
- Park & unpark can unpark first, while wait & notify cannot notify first
- park does not release the lock, while wait releases the lock
7-3 principle
Each thread has its own Park object, and the object_ counter,_ cond,__ mutex composition
-
When park is called before unpark is called
-
Call park first
- When the thread runs, the_ Set the value of counter to 0;
- When park is called, check whether the value of counter is 0. If it is 0, put the thread into the blocking queue cond
- After being placed in the blocking queue, the counter will be set to 0 again
-
Then call unpark.
-
- Call unpark first, then park
- Call unpark
- Will set counter to 1 (runtime 0)
- Call the park method
- Check whether the counter is 0
- Because unpark has set the counter to 1, set the counter to 0 at this time, but do not put it into the blocking queue cond
- Call unpark
8. Re understand thread state transition
Suppose there is a thread Thread t
Case 1: NEW – > runnable
- When the t.start() method is called, NEW – > runnable
Case 2: runnable < – > waiting
- When the t thread is called and the object lock is obtained with synchronized(obj)
- When the obj.wait() method is called, the t thread starts from RUNNABLE – > waiting
- When calling obj.notify(), obj.notifyAll(), t.interrupt()
- The contention lock is successful, and the t thread starts from WAITING – > runnable
- Contention lock failed, t thread from WAITING – > blocked
Case 3: runnable < – > waiting
- When the current thread calls the t.join() method, the current thread starts from RUNNABLE – > waiting
- Note that the current thread is waiting on the monitor of the t thread object
- t when the thread ends running, or the interrupt() of the current thread is called, the current thread starts from WAITING – > runnable
Case 4: runnable < – > waiting
- Calling the LockSupport.park() method by the current thread will cause the current thread to change from RUNNABLE – > waiting
- Calling locksupport.unpark (target thread) or calling interrupt() of the thread will cause the target thread to change from WAITING – > runnable
Case 5: runnable < – > timed_ WAITING
After the t thread obtains the object lock with synchronized(obj)
- When the obj.wait(long n) method is called, the t thread starts from RUNNABLE – > timed_ WAITING
- The waiting time of T thread exceeds n milliseconds, or when calling obj.notify(), obj.notifyAll(), t.interrupt()
- Contention lock successful, t thread from TIMED_WAITING –> RUNNABLE
- Contention lock failed, t thread from TIMED_WAITING –> BLOCKED
Case 6: runnable < – > timed_ WAITING
- When the current thread calls the t.join(long n) method, the current thread starts from RUNNABLE – > timed_ WAITING
- Note that the current thread is waiting on the monitor of the t thread object
- When the waiting time of the current thread exceeds n milliseconds, or the running of the t thread ends, or the interrupt() of the current thread is called, the current thread starts from TIMED_WAITING –> RUNNABLE
Case 7: runnable < – > timed_ WAITING
- The current thread calls Thread.sleep(long n). The current thread starts from RUNNABLE – > timed_ WAITING
- The waiting time of the current thread has exceeded n milliseconds. The current thread is from TIMED_WAITING –> RUNNABLE
Case 8: runnable < – > timed_ WAITING
- When the current thread calls LockSupport.parkNanos(long nanos) or locksupport.parkuntil (long miles), the current thread starts from RUNNABLE – > timed_ WAITING
- Calling locksupport.unpark (target thread), calling interrupt() of the thread, or waiting for timeout will cause the target thread to start from TIMED_WAITING–> RUNNABLE
Case 9: runnable < – > blocked
- When the t thread obtains the object lock with synchronized(obj), if the contention fails, it starts from RUNNABLE – > blocked
- After the synchronization code block of the obj lock thread is executed, it will wake up all BLOCKED threads on the object to compete again. If t threads compete successfully, from BLOCKED – > runnable, other failed threads are still BLOCKED
Case 10: runnable < – > terminated
After all the codes of the current thread have been run, enter TERMINATED
A picture of the art of Java Concurrent Programming is attached here to summarize:
9. Multiple locks
class BigRoom { //Create additional objects as locks private final Object studyRoom = new Object(); private final Object bedRoom = new Object(); }
Subdivide the granularity of locks
- Benefit: it can enhance concurrency
- Disadvantages: if a thread needs to obtain multiple locks at the same time, it is prone to deadlock
10. Activity
10-1 deadlock
There is such a situation: a thread needs to obtain multiple locks at the same time, and deadlock is easy to occur
For example:
- The t1 thread obtains the lock of the A object. Next, it wants to obtain the lock of the B object
- The t2 thread obtains the lock of the B object. Next, it wants to obtain the lock of the A object
Let's look at a deadlock Chestnut:
@Slf4j public class Test26 { public static void main(String[] args) { test1(); } private static void test1() { Object A = new Object(); Object B = new Object(); Thread t1 = new Thread(() -> { synchronized (A) { log.debug("lock A"); Sleeper.sleep(1); synchronized (B) { log.debug("lock B"); log.debug("operation..."); } } }, "t1"); Thread t2 = new Thread(() -> { synchronized (B) { log.debug("lock B"); Sleeper.sleep(1); synchronized (A) { log.debug("lock A"); log.debug("operation..."); } } }, "t2"); t1.start(); t2.start(); } }
Necessary conditions for deadlock generation:
- Mutually exclusive condition. Only the competition for resources that must be mutually exclusive will lead to deadlock (such as philosopher's chopsticks, printer equipment). Resources such as memory and speakers that can be used by multiple processes at the same time will not cause deadlock (because processes do not have to block and wait for such resources).
- Conditions of non deprivation. The resources obtained by a process cannot be forcibly taken away by other processes before they are used up, but can only be released actively.
- Request and hold conditions. The process has maintained at least one resource, but it puts forward a new resource request, and the resource is occupied by other processes. At this time, the requesting process is blocked, but it keeps its existing resources.
- Cycle wait condition. There is a circular waiting chain of process resources. The resources obtained by each process in the chain are requested by the next process at the same time.
If you want to learn more about deadlocks, you can refer to Operating system - deadlock
10-2 positioning deadlock
To detect deadlock, you can use jconsole tool, or use jps to locate the process id, and then use jstack to locate the deadlock
-
jps+jstack ThreadID
-
jconsole
10-3 dining problems of philosophers
Code demonstration:
public class Test27 { public static void main(String[] args) { Chopstick c1 = new Chopstick("1"); Chopstick c2 = new Chopstick("2"); Chopstick c3 = new Chopstick("3"); Chopstick c4 = new Chopstick("4"); Chopstick c5 = new Chopstick("5"); new Philosopher("Socrates", c1, c2).start(); new Philosopher("Plato", c2, c3).start(); new Philosopher("Aristotle", c3, c4).start(); new Philosopher("Heraclitus", c4, c5).start(); new Philosopher("Archimedes", c5, c1).start(); } } /** * Chopsticks */ class Chopstick { String name; public Chopstick(String name) { this.name = name; } @Override public String toString() { return "chopsticks{" + name + '}'; } } /** * Philosophers */ @Slf4j class Philosopher extends Thread { Chopstick left; Chopstick right; public Philosopher(String name, Chopstick left, Chopstick right) { super(name); this.left = left; this.right = right; } public void eat() { log.debug("eating..."); Sleeper.sleep(1); } @Override public void run() { while (true) { while (true) { // Get left-hand chopsticks synchronized (left) { // Get right-hand chopsticks synchronized (right) { // having dinner eat(); } // Put down your right chopsticks } // Put down your left chopsticks } } } } //This code will deadlock
10-4 movable lock
Livelock occurs when two threads change each other's end conditions, and no one can end after the end
for instance:
@Slf4j public class Test28 { static volatile int count = 10; static final Object lock = new Object(); public static void main(String[] args) { new Thread(() -> { while (count > 0) { Sleeper.sleep(0.2); count--; log.debug("count:{}", count); } }, "t1").start(); new Thread(() -> { while (count < 20) { Sleeper.sleep(0.2); count++; log.debug("count:{}", count); } }, "t2").start(); } } //The sleep time of the two can be changed to make them staggered to avoid livelock
The difference between deadlock and livelock:
- Deadlock: it is the phenomenon that threads hold the desired locks of objects and do not release them. Finally, the threads block and stop running.
- Livelock: it is a phenomenon that the code is running all the time but can't finish running because the end condition of each other is modified between threads
10-5 hunger
Because a thread's priority is too low, it can't be scheduled by the CPU for execution and can't end. This phenomenon is hunger
11.ReentrantLock
11-1 features + Grammar
Compared with synchronized, ReentrantLock has the following characteristics:
- Interruptible
- You can set the timeout
- Can be set to fair lock
- Support multiple conditional variables (with multiple waitset s)
Like synchronized, reentrant is supported
Basic syntax:
// Acquire lock reentrantLock.lock(); try { // Critical zone } finally { // Release lock reentrantLock.unlock(); }
11-2 reentrant
- Reentrant means that if the same thread obtains the lock for the first time, it has the right to obtain the lock again because it is the owner of the lock
- If it is a non reentrant lock, you will be blocked by the lock the second time you obtain the lock
Lock reentry example:
@Slf4j public class Test29 { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { lock.lock(); try { log.debug("enter main"); m1(); } finally { lock.unlock(); } } public static void m1() { lock.lock(); try { log.debug("enter m1"); m2(); } finally { lock.unlock(); } } public static void m2() { lock.lock(); try { log.debug("enter m2"); } finally { lock.unlock(); } } } //output 16:10:20 DEBUG [main] (Test29.java:13) - enter main 16:10:20 DEBUG [main] (Test29.java:22) - enter m1 16:10:20 DEBUG [main] (Test29.java:31) - enter m2
11-3 interruptible
If a thread is in a blocking state, you can call its interrupt method to stop blocking and fail to obtain the lock
@Slf4j public class Test30 { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { Thread t1 = new Thread(() -> { try { //If there is no contention, the method acquires the lock object lock //If there is competition, it enters the blocking queue and can be interrupted by other threads with the interrupt method log.debug("Attempt to acquire lock"); lock.lockInterruptibly();//Interruptible lock! This method must be used, and lock() cannot be interrupted } catch (InterruptedException e) { e.printStackTrace(); log.debug("No lock,return");//The process of waiting for the lock is interrupted return; } try { log.debug("Get lock"); } finally { lock.unlock(); } }, "t1"); lock.lock();//The main thread obtains the lock log.debug("Got the lock"); t1.start(); try { sleep(1); t1.interrupt(); log.debug("Execution interrupt"); } finally { lock.unlock(); } } } 16:24:33 DEBUG [t1] (Test30.java:15) - Attempt to acquire lock 16:24:34 DEBUG [main] (Test30.java:31) - interrupt t1 16:24:34 DEBUG [t1] (Test30.java:19) - No lock,return
11-4 lock timeout
- Using the lock.tryLock() method will return whether the lock acquisition was successful. If successful, it returns true; otherwise, it returns false.
- And the tryLock() method can specify the waiting time. The parameters are: tryLock(long timeout, TimeUnit unit), where timeout is the longest waiting time and TimeUnit is the time unit
Improve the previous deadlock problem:
@Override public void run() { while (true) { if (left.tryLock()) { try { if(right.tryLock()){ try { eat(); }finally { right.unlock(); } } } finally { left.unlock(); } } } } //Need to inherit ReentrantLock class Chopstick extends ReentrantLock { String name; public Chopstick(String name) { this.name = name; } @Override public String toString() { return "chopsticks{" + name + '}'; } }
11-5 fair lock
When a thread fails to acquire a lock and enters the blocking queue, the first to enter will obtain the lock after the lock is released. Such access is fair.
The default is unfair lock, which needs to be specified as fair lock when creating:
ReentrantLock lock = new ReentrantLock(true);
Fair locks are generally unnecessary and reduce concurrency
11-6 conditional variables
There are also conditional variables in synchronized, that is, the waitSet lounge when we talk about the principle. When the conditions are not met, enter the waitSet and wait
The advantage of ReentrantLock's conditional variables over synchronized is that it supports multiple conditional variables, which is like
- synchronized means that those threads that do not meet the conditions are waiting for messages in a lounge
- ReentrantLock supports multiple lounges, including a lounge dedicated to waiting for cigarettes and a lounge dedicated to waiting for breakfast. It also wakes up according to the lounge when waking up
Use process:
- You need to obtain a lock before await
- After await is executed, it will release the lock and enter the conditionObject to wait
- await's thread is awakened (or interrupted, or timed out) to re compete for the lock lock
- After the contention lock is successful, the execution continues after await
@Slf4j public class Test80 { static final Object room = new Object(); static boolean hasCigarette = false;//Is there any smoke static boolean hasTakeout = false; static ReentrantLock ROOM = new ReentrantLock(); static Condition waitCigaretteSet = ROOM.newCondition(); static Condition waitTakeoutSet = ROOM.newCondition(); public static void main(String[] args) { new Thread(() -> { ROOM.lock(); try { log.debug("Any smoke?[{}]", hasCigarette); while (!hasCigarette) { log.debug("No smoke, take a break!"); try { waitCigaretteSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("Any smoke?[{}]", hasCigarette); if (hasCigarette) { log.debug("You can start working"); } else { log.debug("No dry survival"); } } finally { ROOM.unlock(); } }, "Xiaonan").start(); new Thread(() -> { ROOM.lock(); try { log.debug("Did you deliver the takeout?[{}]", hasTakeout); while (!hasTakeout) { log.debug("No takeout, take a break!"); try { waitTakeoutSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("Did you deliver the takeout?[{}]", hasTakeout); if (hasTakeout) { log.debug("You can start working"); } else { log.debug("No dry survival.."); } } finally { ROOM.unlock(); } }, "my daughter").start(); Sleeper.sleep(1); new Thread(() -> { ROOM.lock(); try { hasTakeout = true; waitTakeoutSet.signal(); } finally { ROOM.unlock(); } }, "Delivery").start(); Sleeper.sleep(1); new Thread(() -> { ROOM.lock(); try { hasCigarette = true; waitCigaretteSet.signal(); } finally { ROOM.unlock(); } }, "Cigarette delivery").start(); } }
Finally, don't forget to click three times 🍭🍭🍭