JUC is a part of my job that I don't use very much, but I often get asked about it in a helpless interview, so I'll learn about JUC today.
JUC is all called Java. Util. The concurrent package is a toolkit for Java, so why is JUC a frequent question for interviews? Because JUC includes multithreading, atomicity, locks and other heavy content, it can be used to examine a programmer's understanding of Java technology.
1. Review multithreading
Interview 1: Can Java create threads?
Answer: No, Java creation threads call the underlying local native method and C++ to create threads.
Query PC CPU Cores
package com.test.rabbitmq.test; /** * @author ME * @date 2022/2/5 0:41 */ public class Test { public static void main(String[] args) { System.out.println("Of this computer CPU Number of cores" + Runtime.getRuntime().availableProcessors() + "nucleus"); } }
Threads have several States
Name | Meaning |
NEW | newborn |
RUNNABLE | Function |
BLOCKED | block |
WAITING | wait for |
TIMED_WAITING | Timeout Wait |
TERMINATED | termination |
The difference between wait and sleep methods
When I was learning multithreading, I talked about the difference between the two, and I'll repeat here
1. From different classes, wait method from Object class, sleep method from Thread class
Avoid using the sleep method directly by using the following code when sleeping at work
// Sleep one day TimeUnit.DAYS.sleep(1); // Sleep for two seconds TimeUnit.SECONDS.sleep(2);
2. Lock release, wait method releases lock, sleep method does not release lock
3. The wait method must be used in the synchronous code block, otherwise IllegalMonitorStateException will be reported, and the sleep method can be used anywhere
Synchronized Differentiates Actual Warfare from Lock
Let's first create a business logic class for buying tickets
package com.test.rabbitmq.test; /** * @author ME * @date 2022/2/5 15:25 * Ticket Selling Category */ public class Ticket { // Set up a total of 30 tickets private int ticketNum = 30; // Set up ticket selling methods public void sell() { // We can only sell tickets when our remaining votes are greater than 0 if (ticketNum > 0) { // Reduce the number of votes first, then add more, which adds extra logic and makes errors easy ticketNum--; // Reduce the number of votes after each ticket is sold and print the remaining number of votes System.out.println(Thread.currentThread().getName() + "Sell No." + (ticketNum + 1) + "Tickets, Surplus:" + (ticketNum) + "Tickets"); } } }
Then we create a multithreaded call
package com.test.rabbitmq.test; /** * @author ME * @date 2022/2/5 15:25 */ public class MyLockTest { // Main method for execution public static void main(String[] args) { // Object to create ticket Ticket ticket = new Ticket(); // Thread A has a total of 30 people to buy tickets new Thread(() -> { for (int i = 0; i < 30; i++) ticket.sell();}, "A").start(); // Thread B has 30 people to buy tickets new Thread(() -> { for (int i = 0; i < 30; i++) ticket.sell();}, "B").start(); // Thread C has 30 people to buy tickets new Thread(() -> { for (int i = 0; i < 30; i++) ticket.sell();}, "C").start(); } }
The results of this execution are as follows, which results in resource insecurity due to data sharing between threads.
Then we try again after adding the Synchronized keyword to the ticket sales category
package com.test.rabbitmq.test; /** * @author ME * @date 2022/2/5 15:25 * Ticket Selling Category */ public class Ticket { // Set up a total of 30 tickets private int ticketNum = 30; // Set up the method of selling tickets, add synchronized keyword public synchronized void sell() { // We can only sell tickets when our remaining votes are greater than 0 if (ticketNum > 0) { // Reduce the number of votes first, then add more, which adds extra logic and makes errors easy ticketNum--; // Reduce the number of votes after each ticket is sold and print the remaining number of votes System.out.println(Thread.currentThread().getName() + "Sell No." + (ticketNum + 1) + "Tickets, Surplus:" + (ticketNum) + "Tickets"); } } }
The results of the execution are as follows, and you will find that the execution is normal and orderly. The key is that the synchronized keyword adds synchronization, and only one thread can use a resource at a time.
Next we'll use lock locks
This is a new concept, there is a lock in JUC, for lock, in our java. Util. Concurrent. Under the locks package.
Known implementation classes are:
ReentrantLock | Re-lockable |
ReentrantReadWriteLock.ReadLock | Read Lock |
ReentrantReadWriteLock.WriteLock | Write lock |
Let's check the source code for ReentrantLock again
So what is a fair lock and what is an unfair lock?
fair lock | First come, first come, first execute |
Unfair Lock | Queuing is possible and performance is much higher than fair locks, which are unfair locks used by default by synchronized locks and lock locks in JUC |
Lock lock code is as follows
package com.test.rabbitmq.test; import java.util.concurrent.locks.ReentrantLock; /** * @author ME * @date 2022/2/5 15:25 * Ticket Selling Category */ public class Ticket { // Set up a total of 30 tickets private int ticketNum = 30; // Create Lock Object Outside Method ReentrantLock lock = new ReentrantLock(); // Set up ticket selling methods public void sell() { // Lock inside method, followed by tryCatch lock.lock(); try { // We can only sell tickets when our remaining votes are greater than 0 if (ticketNum > 0) { // Reduce the number of votes first, then add more, which adds extra logic and makes errors easy ticketNum--; // Reduce the number of votes after each ticket is sold and print the remaining number of votes System.out.println(Thread.currentThread().getName() + "Sell No." + (ticketNum + 1) + "Tickets, Surplus:" + (ticketNum) + "Tickets"); } }catch (Exception e) { }finally { // Must be unlocked lock.unlock(); } } }
The results are as follows, as does the synchronized keyword
So the question arises, what is the difference between the two?
1.synchronized is a keyword in Java; Lock is a Java class
2.synchronized cannot acquire the state of the lock because it is automatic; Lock can determine if a lock has been acquired
3.synchronized automatically releases the lock because it is automatic; Lock must release the lock manually, if not it will cause a deadlock
4.synchronized One thread gets the lock and the other threads wait all the time; Lock can attempt to acquire a lock through the tryLock() method and do other things
5.synchronized locks are re-entrainable, uninterruptable, and unfair; Lock is a reentrant lock and can determine the state of the lock. Fair and unfair locks can be set by themselves
6.synchronized lock is not flexible; Lock locks are more flexible
Producer-consumer issues
We use the synchronized and wait() and notify() methods to communicate between threads with the following code
Let's first understand the difference between a notify() and a notifyAll() method
notify() is a random wake-up of a thread; notifyAll() is to wake up all waiting threads to contend for locks
Notfy() performs better than notifyAll().
So when do you use the notifyAll() method?
This has to mention the relationship between the lock pool and the wait pool. When we start threads, threads enter the lock pool to contend for locks. When we execute the wait() method, threads entering the wait pool do not participate in the lock contention. Only when the notify() or notifyAll() method is executed, threads wake up from the wait pool to join the lock pool to contend for locks. Once there are no threads in the lock pool, the thread waked up by the notify() method also goes to the wait() method, where all threads are waiting, causing a deadlock.
Let's start by creating two methods, which you need to understand
package com.test.rabbitmq.lockTest; /** * @author ME * @date 2022/2/5 20:26 */ public class A { private int number = 0; // Method 1, wait when numbers are not zero, perform a + 1 operation when numbers are equal to zero, then release the lock and wake up the other waiting threads public synchronized void increment() throws InterruptedException { if (number != 0) { // Make Thread Wait this.wait(); } number++; System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number); // Wake up other threads this.notifyAll(); } // Method 2, wait when number 0, do -1 when number 0 is not, then release the lock, wake up other waiting threads public synchronized void decrement() throws InterruptedException { if (number == 0) { // Make Thread Wait this.wait(); } number--; System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number); // Wake up other threads this.notifyAll(); } }
Then we start these two methods with multithreading
package com.test.rabbitmq.lockTest; /** * @author ME * @date 2022/2/5 20:31 */ public class Test { public static void main(String[] args) { A a = new A(); // Thread A new Thread(() -> { try { for (int i = 0; i < 10; i++) a.increment(); } catch (InterruptedException e) { e.printStackTrace(); } }, "A").start(); // Thread B new Thread(() -> { try { for (int i = 0; i < 10; i++) a.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } }, "B").start(); } }
The result is as follows, which enables communication between threads
But let's add two more threads, with the following code
package com.test.rabbitmq.lockTest; /** * @author ME * @date 2022/2/5 20:31 */ public class Test { public static void main(String[] args) { A a = new A(); // Thread A new Thread(() -> { try { for (int i = 0; i < 10; i++) a.increment(); } catch (InterruptedException e) { e.printStackTrace(); } }, "A").start(); // Thread B new Thread(() -> { try { for (int i = 0; i < 10; i++) a.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } }, "B").start(); // Thread C new Thread(() -> { try { for (int i = 0; i < 10; i++) a.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } }, "C").start(); // Thread D new Thread(() -> { try { for (int i = 0; i < 10; i++) a.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } }, "D").start(); } }
The results after execution are as follows
It's because there's a fake wake-up problem
if only once, while still loops while waiting
Code if succeeded after replacing while
package com.test.rabbitmq.lockTest; /** * @author ME * @date 2022/2/5 20:26 */ public class A { private int number = 0; // Method 1, wait when numbers are not zero, perform a + 1 operation when numbers are equal to zero, then release the lock and wake up the other waiting threads public synchronized void increment() throws InterruptedException { while (number != 0) { // Make Thread Wait this.wait(); } number++; System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number); // Wake up other threads this.notifyAll(); } // Method 2, wait when number 0, do -1 when number 0 is not, then release the lock, wake up other waiting threads public synchronized void decrement() throws InterruptedException { while (number == 0) { // Make Thread Wait this.wait(); } number--; System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number); // Wake up other threads this.notifyAll(); } }
The results are as follows
The code above uses the synchronized and wait() and notify() methods to communicate between threads, so is there an equivalent alternative in JUC? The answer is yes, if you replace synchronized with Lock in JUC, then other methods can also be replaced with await() and signal().
Below is a comparison of the two codes
package com.test.rabbitmq.lockTest; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * @author ME * @date 2022/2/5 20:26 */ public class A { private int number = 0; // Use synchronized + wait + notify mode // Method 1, wait when numbers are not zero, perform a + 1 operation when numbers are equal to zero, then release the lock and wake up the other waiting threads public synchronized void increment() throws InterruptedException { while (number != 0) { // Make Thread Wait this.wait(); } number++; System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number); // Wake up other threads this.notifyAll(); } // Use lock lock + await + signal // Method 2, wait when number 0, do -1 when number 0 is not, then release the lock, wake up other waiting threads // Create Lock Object ReentrantLock lock = new ReentrantLock(); // Create object monitor objects that signal() can wake up a specified thread compared to notify() Condition condition = lock.newCondition(); public void decrement() throws InterruptedException { lock.lock(); try { while (number == 0) { // Make Thread Wait condition.await(); } number--; System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number); // Waking up other threads is the same as notify-notifyAll, JUC has signal-signalAll condition.signalAll(); }catch (Exception e) { }finally { lock.unlock(); } } }
How the condition class specifies thread wakeup
We learned above that notify() can only wake up randomly, and conditions can wake up as specified, so how does it work? The code is as follows, specifying wake-up after distinguishing by creating multiple Condition classes
package com.test.rabbitmq.lockTest; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * @author ME * @date 2022/2/5 20:26 */ public class A { private int number = 0; // Create Lock Object ReentrantLock lock = new ReentrantLock(); // Create increment object monitor object Condition incrementCondition = lock.newCondition(); // Create decrement object monitor object Condition decrementCondition = lock.newCondition(); // Use lock lock + await + signal // Method 1, wait when numbers are not zero, perform a + 1 operation when numbers are equal to zero, then release the lock and wake up the other waiting threads public void increment() throws InterruptedException { lock.lock(); try { while (number != 0) { // The object monitor of this method performs thread wait incrementCondition.await(); } number++; System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number); // Waking up other threads is the same as notify-notifyAll, JUC has signal-signalAll // Specify wake decrement method decrementCondition.signal(); }catch (Exception e) { }finally { lock.unlock(); } } // Use lock lock + await + signal // Method 2, wait when number 0, do -1 when number 0 is not, then release the lock, wake up other waiting threads public void decrement() throws InterruptedException { lock.lock(); try { while (number == 0) { // The object monitor of this method performs thread wait decrementCondition.await(); } number--; System.out.println(Thread.currentThread().getName() + "Execution complete, current number The value is:" + number); // Waking up other threads is the same as notify-notifyAll, JUC has signal-signalAll // Specify wake up increment method incrementCondition.signal(); }catch (Exception e) { }finally { lock.unlock(); } } }
Common problem solving for locks
1. The synchronized method of the same object is called in multiple threads, starting with the first execution
Reason: The object of a synchronized lock is the calling object of a method. Whoever calls this method locks whoever. If two objects are created, they will not affect each other, or the execution of a common method will not have to wait. However, if the same object is used, even if the sleep() method is used to sleep, other threads will have to wait for the thread's sleep time to finish execution before executing it.
When synchronized locks are static methods, the lock is a class object, locking the class. He differs from the lock normal method in that when synchronized locks the method, even if two objects are created, they affect each other because they share a class class class.
In the same class, when synchronized locks a class, it does not interfere with its instance object, and the instance object locks normally.