Multithreading advanced = JUC concurrent programming
1. What is JUC
JUC is short for java.util.concurrent.
To sum up in Chinese, JUC means java Concurrent Programming toolkit.
The essence of concurrent programming is to make full use of CPU resources.
2. Threads and processes
Process: it is a single sequential control process in program execution. It is the smallest unit of program execution flow and the basic unit of processor scheduling and dispatching.
Thread: it is a single sequential control flow in program execution. It is the smallest unit of program execution flow and the basic unit of processor scheduling and dispatching.
For example, if you start a process, Typora takes notes and waits for a few minutes, it will be automatically saved (the operation of automatic saving is completed by the thread.)
A process can contain multiple threads, including at least one thread
Java has two threads by default: main thread and GC thread (garbage collection thread)
Can Java start threads?
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } //This is a C + + bottom layer. Java has no permission to operate the bottom hardware private native void start0();
Java does not have permission to start threads and operate hardware. This is a native local method that calls the underlying C + + code.
Concurrency, concurrency
Concurrency: refers to the rapid rotation of multiple thread tasks on the same CPU. Because the switching speed is very fast, it gives the impression that these thread tasks are carried out at the same time, but in fact, concurrency is only carried out at the same time in logic;
Parallel: refers to the simultaneous execution of multiple thread tasks on different CPU s, which is really executed at the same time.
The essence of concurrent programming: make full use of CPU resources.
6 states of threads
New, run, block, wait, timeout wait, terminate
public enum State { /** * Thread state for a thread which has not yet started. */ //newly build NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ //function RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ //block BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ //wait for WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ //Timeout wait TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ //termination TERMINATED; }
The difference between wait and sleep
wait | sleep | |
---|---|---|
synchronization | It can only be invoked in the synchronous context, otherwise the IllegalMonitorStateExcepton exception is thrown. | No need to call in synchronized block or synchronous block. |
Action object | The wait method is defined in the Object class Object and acts on the Object itself | The sleep method is defined in java,lang.Thread and acts on the current thread |
Release lock resource | yes | no |
Wakeup condition | Other threads call the notify() or notifyAll() methods | Timeout or call interrupt() method body |
Method properties | wait is an instance method | sleep is a static method |
In general, dormancy is used in enterprises
TimeUnit.DAYS.sleep(1); //Dormancy for 1 day TimeUnit.SECONDS.sleep(1); //Sleep for 1s
3. Lock lock (key)
3.1. Reentrant lock (recursive lock)
3.1.1 synchronized lock
/** * True multithreaded development * Thread is a separate resource class without any affiliated operations! */ public class SaleTicketDemo01 { public static void main(String[] args) { //Multithreading operation //Concurrency: multiple threads operate on the same resource class and throw the resource class into the thread Ticket ticket = new Ticket(); //@Functional interface lambda expression after jdk1.8 new Thread(()->{ for(int i=0;i<40;i++){ ticket.sale(); } },"A").start(); new Thread(()->{ for(int i=0;i<40;i++){ ticket.sale(); } },"B").start(); new Thread(()->{ for(int i=0;i<40;i++){ ticket.sale(); } },"C").start(); } } //Resource class //Attribute + method //oop class Ticket{ private int number=50; //How to sell tickets // synchronized essence: queue, lock public synchronized void sale(){ if(number>0){ System.out.println(Thread.currentThread().getName()+" Sold the third"+number+" Ticket,surplus:"+number+" Ticket"); number--; } } }
3.1.2 Lock interface
Use ReentrantLock (Lock interface is implemented) to synchronize:
Reentrant locks are also called recursive locks, which means that after the outer function of the same thread obtains the lock, the inner recursive function still has the code to obtain the lock, but it is not affected.
It's like cooking in the canteen. You line up at the window. When it came to you, suddenly your roommate A asked you to bring A meal along the way, and then you beat two meals; Before you left the window, roommate B asked you to make another soup, so you made another soup.
lock() method: lock
unlock() method: release the lock
public class Restaurant { private Lock windows = new ReentrantLock(); public void getMeals() throws Exception { try { windows.lock(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "Make rice"); } finally { windows.unlock(); } } public void getSoup() throws Exception { try { windows.lock(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "Make soup"); } finally { windows.unlock(); } } public void today() throws Exception { try { windows.lock(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "Make rice"); getMeals(); getSoup(); } finally { windows.unlock(); } } public static void main(String[] args) { Restaurant test = new Restaurant(); new Thread(() -> { try { test.today(); } catch (Exception e) { e.printStackTrace(); } }, "I").start(); new Thread(() -> { try { test.getSoup(); } catch (Exception e) { e.printStackTrace(); } }, "someone").start(); new Thread(() -> { try { test.getMeals(); } catch (Exception e) { e.printStackTrace(); } }, "Another person").start(); } } Output: I cook I cook I'll make soup Make soup for sb Another man is cooking
3.2. Non reentrant lock (spin lock)
This is not the case in another window where dishes are delicious and popular. Here, you can only order one dish in the window (enter the primary critical area). After ordering, if you want to order other dishes, you can only queue up again (although you can jump in the queue, of course, we can introduce the waiter team management mechanism: private Lock windows = new ReentrantLock(true) ;, specifying that the lock is fair.)
That is, spin lock is a lock specially introduced to prevent multiprocessor concurrency. It is widely used in interrupt processing and other parts of the kernel.
public class Restaurant { boolean isLock = false; public synchronized void getMeals() throws Exception { while (isLock) { wait(); } isLock = true; try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "Make rice"); } finally { isLock = false; } } public synchronized void getSoup() throws Exception { while (isLock) { wait(); } isLock = true; try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "Make soup"); } finally { isLock = false; } } public void today() throws Exception { while (isLock) { wait(); } isLock = true; try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "Make rice"); getSoup(); } finally { isLock = false; } } public static void main(String[] args) { Restaurant test = new Restaurant(); new Thread(() -> { try { test.today(); } catch (Exception e) { e.printStackTrace(); } }, "I").start(); new Thread(() -> { try { test.getSoup(); } catch (Exception e) { e.printStackTrace(); } }, "someone").start(); new Thread(() -> { try { test.getMeals(); } catch (Exception e) { e.printStackTrace(); } }, "Another person").start(); } } Output: I cook Then it deadlocked
3.3 read write lock
ReentrantReadLock and ReentrantReadLock
However, with a large flow of people at meals, the boss found that he often lined up a long line, but the chefs were idle. The boss patted his head and thought that this would not work, so he slightly improved the way of ordering. Everyone can scan the QR code and order with the web page. As long as the dish is not being cooked (write lock), you can order it at will.
That is: suppose your program involves reading and writing to some shared resources, and the writing operations are not as frequent as the reading operations. When there is no write operation, there is no problem for two threads to read a resource at the same time, so multiple threads should be allowed to read shared resources at the same time. However, if a thread wants to write these shared resources, no other thread should read or write to the resource.
public class Restaurant { private ReentrantReadWriteLock food = new ReentrantReadWriteLock(); private ReentrantReadWriteLock.ReadLock getFoodLock = food.readLock(); private ReentrantReadWriteLock.WriteLock cookingLock = food.writeLock(); public void getFood() throws Exception { try { getFoodLock.lock(); System.out.println(Thread.currentThread().getName() + "Order rice"); } finally { getFoodLock.unlock(); } } public void cooking() throws Exception { try { cookingLock.lock(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "cook a dish"); } finally { cookingLock.unlock(); } } public static void main(String[] args) { Restaurant test = new Restaurant(); for (int i = 0; i < 10; i++) { new Thread(() -> { try { test.getFood(); } catch (Exception e) { e.printStackTrace(); } }, "someone").start(); if (i == 2) { new Thread(() -> { try { test.cooking(); } catch (Exception e) { e.printStackTrace(); } }, "cook").start(); } } } } Output: Order dinner for sb Order dinner for sb Order dinner for sb Cook ==Wait 1 second== Order dinner for sb Order dinner for sb Order dinner for sb Order dinner for sb Order dinner for sb Order dinner for sb Order dinner for sb
3.4 fair lock and unfair lock
Fair lock: it is very fair and must abide by the principle of first come, first served;
Unfair lock: it's very unfair. You can jump in the queue.
If no parameters are added when creating a lock, the default is an unfair lock
//If the parameter is true, it is a fair lock; otherwise, it is a non fair lock. private Lock windows = new ReentrantLock(true);
3.5 difference between synchronized and Lock
synchronized | Lock | |
---|---|---|
Existence hierarchy | java keyword, at the JVM level | Is a class |
Lock release | When the thread that obtains the lock executes the synchronization code or the thread execution exception occurs, the lock will be released immediately | The lock must be released in finally, otherwise it may cause deadlock |
Lock acquisition | Suppose thread A obtains the lock and thread B waits. If thread A is blocked, thread B will wait all the time | Lock has four methods to obtain locks, but threads can try to obtain locks through class methods, so they don't have to wait all the time. |
Lock state | Unable to judge | Can judge |
Lock type | Reentrant lock, non interruptible, unfair | Reentrant, interruptible and fair (or unfair, set by yourself) |
performance | Little synchronization | Massive synchronization |
Detailed description of the performance differences between synchronized and Lock
**synchronized originally adopted the CPU pessimistic lock mechanism, that is, the thread obtained an exclusive lock** An exclusive lock means that other threads can only rely on blocking to wait for the thread to release the lock. When the CPU conversion thread is blocked, it will cause thread context switching. When many threads compete for locks, it will cause frequent context switching of the CPU, resulting in low efficiency.
Lock uses optimistic locking. The so-called optimistic lock is to complete an operation without locking each time, assuming that there is no conflict. If the conflict fails, try again until it succeeds. The mechanism of optimistic lock implementation is CAS operation (Compare and Swap).
In terms of performance, if the competition for resources is not fierce, the performance of the two is similar. When the competition for resources is very fierce (that is, a large number of threads compete at the same time), the performance of Lock is much better than that of Synchronized. Therefore, Synchronized is suitable for locking a small number of code synchronization problems, and Lock is suitable for locking a large number of synchronization codes;
3.6 what is the lock?
In a static method, it actually locks the object calling the method, commonly known as "object lock".
In non static methods, this kind of object is actually locked, commonly known as "class lock".
There can be many object instances of a class, but each class has only one class object, so the object locks of different object instances do not interfere with each other, but each class has only one class lock.
However, class lock is only a conceptual thing, not real. It is only used to help us understand the difference between locking instance methods and static methods.
4. Producer and consumer issues
4.1. Traditional version of synchronized
We use synchronized here to solve the producer consumer problem. For example, when num is 0, make thread B wait, wake up thread A to add 1, when num is 1, make thread A wait, and wake up thread B to reduce 1.
/** * Problematic code **/ public class PCSyn{ public static void main(String[] args){ // Get resource object Data data = new Data(); // Let it execute ten times for(int i = 0; i < 10; i ++){ // A thread performs an add operation new Thread(()->{data.increment();},"A").start(); // Thread B performs subtraction new Thread(()->{data.decrement();},"B").start(); } } } class Data{ // Semaphore private int num = 0; // Add 1 public synchronized void increment(){ // Make the thread wait when num is not 0 if(num != 0) this.wait(); // Perform business operations num ++; System.out.println(Thread.currentThread().getName() + "->" + num); // Execution complete, wake up notifyAll(); } public synchronized void decrement(){ // Let the thread wait when num is 0 if(num == 0) this.wait(); // Perform business operations num --; System.out.println(Thread.currentThread().getName() + "->" + num); // Execution complete, wake up notifyAll(); } }
So far, when two threads AB execute, the result is indeed 01 alternately as we expected. However, in the same case, if we add two threads CD to the above code to perform the same operation, that is, thread AC performs + 1 operation and thread BD performs - 1 operation.
At this point, the results are not as good as we want:
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-21oyqld2-163205312970) (C: \ users \ dad \ appdata \ roaming \ typora \ typora user images \ image-20210913102336420. PNG)]
We can see that when num changes due to the last operation value, we judge if only once. When this condition is met, the program continues to execute. For example, if num is 0 at this time, thread A and thread C are awakened, but only one thread is useful, and the other is false wake-up.
False wake-up: in the process of thread waiting / wake-up, after the waiting thread is awakened, it still continues to run downward when the conditions are not met.
We only need to replace the if in the code with while to solve the problem of false wake-up. Because you still need to judge the condition again after waking up, and if runs directly.
public class PCSyn{ public static void main(String[] args){ // Get resource object Data data = new Data(); // Let it execute ten times for(int i = 0; i < 10; i ++){ // A thread performs an add operation new Thread(()->{data.increment();},"A").start(); // Thread B performs subtraction new Thread(()->{data.decrement();},"B").start(); } } } class Data{ // Semaphore private int num = 0; // Add 1 public synchronized void increment(){ // Make the thread wait when num is not 0 while(num != 0) { this.wait(); } // Perform business operations num ++; System.out.println(Thread.currentThread().getName() + "->" + num); // Execution complete, wake up notifyAll(); } public synchronized void decrement(){ // Let the thread wait when num is 0 while(num == 0) { this.wait(); } // Perform business operations num --; System.out.println(Thread.currentThread().getName() + "->" + num); // Execution complete, wake up notifyAll(); } }
4.2,Lock
The same problem can be implemented through the Lock interface.
public class PCLock{ public static void main(String[] args){ Data data = new Data(); for(int i = 0; i < 10; i ++){ new Thread(()->{data.increment();},"A").start(); new Thread(()->{data.decrement();},"B").start(); new Thread(()->{data.increment();},"C").start(); new Thread(()->{data.decrement();},"D").start(); } } } class Data{ private int num = 0; private Lock lock = new ReentrantLock(); // Unlike synchronized, the wait and wake-up of lock correspond to await() and signalAll() respectively in the condition object private Condition condition = lock.newCondition(); public void increment(){ lock.lock(); try{ while(num != 0) { condition.await(); } num++; System.out.println(Thread.currentThread().getName() + "->" + num); condition.signalAll(); } catch(Exception e){ e.printStackTrace(); } finally{ lock.unlock(); } } public void decrement(){ lock.lock(); try{ while(num == 0) { condition.await(); } num--; System.out.println(Thread.currentThread().getName() + "->" + num); condition.signalAll(); } catch(Exception e){ e.printStackTrace(); } finally{ lock.unlock(); } } }
However, why introduce a new technology for the problems that can be solved by synchronized? This technology must have its own excellence.
In the Lock condition, we can accurately control which thread is awakened and which thread is waiting. For example, thread A prints A, thread B prints B, and thread C prints C, and the order of each occurrence is A - > b - > C.
Advantages of condition: accurate notification and wake-up threads.
public class PrintLock{ public static void main(String[] args){ Data data = new Data(); for(int i = 0; i < 10; i ++){ new Thread(()->{data.printA();},"A").start(); new Thread(()->{data.printB();},"B").start(); new Thread(()->{data.printC();},"C").start(); } } } class Data{ private int num = 1; // 1.A thread 2.B thread 3.C thread private Lock lock = new ReentrantLock(); // Accurately wake up or wait for a thread through multiple synchronization monitors private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); public void printA(){ lock.lock(); try{ while(num != 1) conditino1.await(); System.out.println(Thread.currentThread().getName() + "->AAA"); // Wake up B thread condition2.signal(); } catch(Exception e){ e.printStackTrace(); } finally{ lock.unlock(); } } public void printB(){ lock.lock(); try{ while(num != 2) conditino1.await(); System.out.println(Thread.currentThread().getName() + "->BBB"); // Wake up C thread condition3.signal(); } catch(Exception e){ e.printStackTrace(); } finally{ lock.unlock(); } } public void printA(){ lock.lock(); try{ while(num != 3) conditino1.await(); System.out.println(Thread.currentThread().getName() + "->CCC"); // Wake up A thread condition1.signal(); } catch(Exception e){ e.printStackTrace(); } finally{ lock.unlock(); } } }
5. And 8 locking phenomenon
The following terms shall be interpreted:
Sequential execution: the first call is executed first;
Random execution: there is no rule, which is related to computer hardware resources. Which thread gets the resources first will execute first, and each thread will not interfere with each other
5.1. Multiple threads use the same lock - sequential execution
Example 1. For standard access, would you like to print email or send SMS first
public class MultiThreadUseOneLock01 { public static void main(String[] args){ Mobile mobile = new Mobile(); // Two threads use the same object. Two threads are a lock! Call first, execute first! new Thread(()->mobile.sendEmail(),"A").start(); // interfere try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->mobile.sendMS(),"B").start(); } } // Mobile phone, text message, email class Mobile { // The object of the synchronized modified method and lock is the caller of the method public synchronized void sendEmail() { System.out.println("sendEmail"); } public synchronized void sendMS() { System.out.println("sendMS"); } }
Answer: send e-mail first, and then send text messages.
5.2. Multiple threads use the same lock, and there is blocking in one thread - sequential execution
Example 2. The email method is suspended for 4 seconds. Would you like to print email or SMS first?
public class MultiThreadUseOneLock02 { public static void main(String[] args){ Mobile2 mobile = new Mobile2(); // Two threads use the same object. Two threads are a lock! Call first, execute first! new Thread(()->mobile.sendEmail(),"A").start(); // interfere try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->mobile.sendMS(),"B").start(); } } // Mobile phone, text message, email class Mobile2 { // The object of the synchronized modified method and lock is the caller of the method public synchronized void sendEmail() { //Multiple threads use a lock. Here, set an interference try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendEmail"); } public synchronized void sendMS() { System.out.println("sendMS"); } }
Answer: send e-mail first and then text messages.
5.3. Multiple threads, some with locks and some without locks - random execution
Example 3. Add a new common method to receive wechat () without synchronization. Would you like to print mail or receive wechat first?
public class MultiThreadHaveLockAndNot03 { public static void main(String[] args){ Mobile3 mobile = new Mobile3(); // Two threads use the same object. Two threads are a lock! Call first, execute first! new Thread(()->mobile.sendEmail(),"A").start(); // interfere try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->mobile.sendMS(),"B").start(); new Thread(()->mobile.getWeixinMs(),"C").start(); } } // Mobile phone, text message, email class Mobile3 { // The object of the synchronized modified method and lock is the caller of the method public synchronized void sendEmail() { //Multiple threads use a lock. Here, set an interference try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendEmail"); } public synchronized void sendMS() { System.out.println("sendMS"); } //Receive wechat without lock public void getWeixinMs() { System.out.println("getWeixinMs"); } }
Answer: first execute the getWeixinMs() method, then send e-mail, and finally send text messages. The reason is that the getWeixinMs() method is a normal method and is not affected by Synchronized locks.
5.4. Multiple threads use multiple rake locks - random execution
For a method modified by Synchronized, the object of the lock is the caller of the method;
Different from callers, they do not use the same lock and have no relationship with each other.
Example 4. Two mobile phones. Would you like to print email or SMS first?
public class MultiThreadUseMultiLock04 { public static void main(String[] args){ // Two objects do not interfere with each other Mobile4 mobile1 = new Mobile4(); Mobile4 mobile2 = new Mobile4(); new Thread(()->mobile1.sendEmail(),"A").start(); // interfere try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->mobile2.sendMS(),"B").start(); } } // Mobile phone, text message, email class Mobile4 { /** * @description: * For a method modified by synchronized, the object of the lock is the caller of the method; * Different from callers, they do not use the same lock and have no relationship with each other. */ public synchronized void sendEmail() { //Set an interference here try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendEmail"); } public synchronized void sendMS() { System.out.println("sendMS"); } }
Answer: send text messages first, then email.
5.5. Class lock: multiple threads use one object - sequential execution
For the method modified by Synchronized and static at the same time, the lock object is the class object of the class, which is the only lock, and the threads execute sequentially.
The difference between lock Class and lock object:
1. Class lock. There is only one class template;
-
Object lock. You can new multiple objects through the class template.
If all classes are locked, all objects under this Class have the same lock.
Example 5. Two static synchronization methods, the same mobile phone, print email or SMS first?
public class MultiThreadUseOneObjectOneClassLock05 { public static void main(String[] args){ Mobile5 mobile = new Mobile5(); new Thread(()->mobile.sendEmail(),"A").start(); // interfere try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->mobile.sendMS(),"B").start(); } } // Mobile phone, text message, email class Mobile5 { public synchronized static void sendEmail() { //Set an interference here try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendEmail"); } public synchronized static void sendMS() { System.out.println("sendMS"); } }
Answer: send e-mail first, then send text messages.
5.6. Class lock: multiple threads use multiple objects - sequential execution
For methods modified by synchronized and static, the lock object is the class object of the class, which is the only lock.
Class lock is unique, so multiple objects use the same class lock.
Example 6. Two static synchronization methods, two mobile phones. Would you like to print email or SMS first?
public class MultiThreadUseMultiObjectOneClassLock06 { public static void main(String[] args){ Mobile6 mobile1 = new Mobile6(); Mobile6 mobile2 = new Mobile6(); new Thread(()->mobile1.sendEmail(),"A").start(); // interfere try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->mobile2.sendMS(),"B").start(); } } // Mobile phone, text message, email class Mobile6 { public synchronized static void sendEmail() { //Set an interference here try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendEmail"); } public synchronized static void sendMS() { System.out.println("sendMS"); } }
Answer: send e-mail first, then send text messages. Objects created first are executed first.
5.7. Class lock and object lock: multiple threads use one object - random execution
For the method modified by synchronized and static, the lock object is the class object of the class! The only same lock;
Methods that are only modified by synchronized are ordinary locks (such as object locks), not Class locks, so the execution order between processes does not interfere with each other.
Example 7. A common synchronization method, a static synchronization method, and the same mobile phone. Would you like to print email or SMS first?
public class MultiThreadUseOneObjectClassLockAndObjectLock07 { public static void main(String[] args){ Mobile7 mobile = new Mobile7(); new Thread(()->mobile.sendEmail(),"A").start(); // interfere try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->mobile.sendMS(),"B").start(); } } // Mobile phone, text message, email class Mobile7 { public synchronized static void sendEmail() { //Set an interference here try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendEmail"); } /** * @description: * Common synchronization lock: object lock */ public synchronized void sendMS() { System.out.println("sendMS"); } }
Answer: send text messages first, then email.
5.8. Class lock and object lock: multiple threads use multiple objects - random execution
For the method modified by synchronized and static, the lock object is the class object of the class! The only same lock;
Methods that are only modified by synchronized are ordinary locks (such as object locks), not Class locks, so the execution order between processes does not interfere with each other.
Example 8. A common synchronization method, a static synchronization method and two mobile phones. Would you like to print email or SMS first?
public class MultiThreadUseMultiObjectClassLockAndObjectLock08 { public static void main(String[] args){ Mobile8 mobile1 = new Mobile8(); Mobile8 mobile2 = new Mobile8(); new Thread(()->mobile1.sendEmail(),"A").start(); // interfere try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->mobile2.sendMS(),"B").start(); } } // Mobile phone, text message, email class Mobile8 { public synchronized static void sendEmail() { //Set an interference here try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("sendEmail"); } /** * @description: * Common synchronization lock: object lock */ public synchronized void sendMS() { System.out.println("sendMS"); } }
Answer: send text messages first, then email.
6. Collection class unsafe
6.1. Unsafe
A collection class with the following List:
public class ListTest { public static void main(String[] args) { List<Object> arrayList = new ArrayList<>(); for(int i=1;i<=10;i++){ new Thread(()->{ arrayList.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(arrayList); },String.valueOf(i)).start(); } } }
This will cause: [the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-e1oDKhdb-1632053112972)(C:\Users \ dad \ appdata \ roaming \ typora \ user images \ image-20210914212930786. PNG)]
This is caused by a thread lock.
Solution 1: use vector (but it is not recommended because it is published earlier than ArrayList)
List<String> list = new Vector<>();
Solution 2: use the tool class Collections to make ArrayList secure
List<String> list = Collections.synchronizedList(new ArrayList<>());
Solution 3: use copyonwritearraylist < > () under JUC package;
List<String> list = new CopyOnWriteArrayList<>();
CopyOnWrite: copy on write, referred to as COW. An optimization strategy in the field of computer programming
Reason: when multiple threads call, the List is fixed when reading, and there is an overwrite problem when writing. Therefore, when writing, copy a container and write again (and lock the write operation) to avoid write overwrite.
CopyOnWriteArrayList is better than Vector. Where is it?
The underlying Vector is implemented using the synchronized keyword: it is particularly inefficient.
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-z2enyq1h-163205312973) (C: \ users \ dad \ appdata \ roaming \ typora \ typora user images \ image-20210914214309523. PNG)]
CopyOnWriteArrayList uses Lock lock, which will be more efficient!
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-xqqhl3lz-163205312975) (C: \ users \ dad \ appdata \ roaming \ typora \ typera user images \ image-20210914214838168. PNG)]
6.2. Unsafe
There are the following collection classes:
public class SetTest { public static void main(String[] args) { //Set<String> set = new HashSet<>(); //Set<String> set = Collections.synchronizedSet(new HashSet<>()); Set<String> set = new CopyOnWriteArraySet<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); },String.valueOf(i)).start(); } } }
Solution 1: use the tool class Collections to make the HashSet secure
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Solution 2: use copyonwritearrayset < > () under JUC package;
List<String> list = new CopyOnWriteArraySet<>();
What is the bottom layer of HashSet?
The bottom layer of HashSet is a HashMap;
public HashSet() { map = new HashMap<>(); } public boolean add(E e) { return map.put(e, PRESENT)==null; } private static final Object PRESENT = new Object();
In essence, add is a map key. Map keys cannot be repeated, so map storage is used.
hashSet uses the principle that HashMap key s cannot be repeated.
What is PRESENT? Is a constant that will not change. It is a useless placeholder of the constant.
6.3. Unsafe Map
Review the basic operation of Map;
What is the default equivalent? new HashMap<>(16, 0.75);
Map<String, String> map = new HashMap<>(); // Load factor, initialization capacity
The default load factor is 0.75 and the default initial capacity is 16
There are the following Map collection classes:
public class MapTest { public static void main(String[] args) { //Is map used like this? What is the default equivalent// You don't have to work, //Map<String, String> map = new HashMap<>(); //Default: HashMap < string, string > map = new HashMap < > (16,0.75) //Loading factor, initial capacity //Map<String, String> map = Collections.synchronizedMap(new HashMap()); Map<String, String> map = new ConcurrentHashMap<>(); for (int i = 0; i < 30; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } }
Solution 1: use the tool class Collections to make HashMap < > () secure
Map<String, String> map = Collections.synchronizedMap(new HashMap());
Solution 2: use the concurrenthashmap under the JUC package < > ();
Map<String, String> map = new ConcurrentHashMap<>();
7. Callable (simple)
Difference from Runnable:
1. Return value is allowed
2. Exceptions can be thrown
3. Different method names, run()/call()
Imagine this situation, there are many threads. We want to pick out the thread with problems. If we follow the previous method, it is very troublesome. However, if we use the Callable interface, we can easily solve this problem by allowing the thread with no problems to return true and the thread with problems to return false.
Let's see how to create a thread using Callable:
But how to put Callable into Thread?
Source code analysis:
Next, we look at the Runnable interface and its sub interfaces and implementation classes, and find that there is a sub interface called RunnableFuture
Then check RunnableFuture and find that it has an implementation class called FutureTask
The FutureTask constructor has a Callable interface:
Therefore, we pass the Callable interface into the FutureTask constructor, and FutureTask itself implements the Runnable interface, so Callable and Runnable are online, that is, we pass FutureTask into the Thread constructor.
Therefore, the correct way to create a multithread using the Callable interface is:
public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { for (int i = 1; i < 10; i++) { // new Thread(new Runnable()).start(); // new Thread(new FutureTask<>( Callable)).start(); MyThread thread= new MyThread(); //Adaptation class: FutureTask FutureTask<String> futureTask = new FutureTask<>(thread); //Put into Thread for use new Thread(futureTask,String.valueOf(i)).start(); //Get return value String s = futureTask.get(); System.out.println("Return value:"+ s); } } } class MyThread implements Callable<String> { @Override public String call() throws Exception { System.out.println("Call:"+Thread.currentThread().getName()); return "String"+Thread.currentThread().getName(); } }
Let's look at the return value of Callable
Use the get() method to get the return value.
futureTask.get();
Question Thinking: the Callable interface adds a return value to the thread. Why do you have to find a get method to get it? Can't you get it directly after the thread runs?
Analysis: imagine that there are four threads in the main thread, namely A, B, C and D. Suppose that the execution time of B is 20s and that of other threads is 5s. If we obtain the return value in the following figure, it will cause A waste of time:
The above figure shows that after each thread is started, it uses the get method to wait for the return value, so the main thread will not run down until the return value of the get method is obtained. Therefore, for each thread, it will get other threads only after its own operation, which is the same as sequential execution.
If all get methods are placed at the end of the program, as shown in the figure below, the execution time can be shortened:
As shown in the figure above, we let all threads run first and didn't get the return value immediately. Therefore, getting the value of A.get() means that 5s has passed, and ABCD4 threads execute simultaneously within 5s, while B slows down the total execution time because the execution time is 20s.
The same FutureTask object can only be called once by different threads
There is only one futureTask object, so even if two threads A and B call, it will be executed only once.
FutureTask futureTask = new FutureTask(new MyThread()); new Thread(futureTask, "A").start(); new Thread(futureTask, "B").start();
8. Common auxiliary classes (required!!!)
8.1, CountDownLatch
In fact, it is a subtraction counter. For the following operations after the counter is reset to zero, this is a counter!
//This is a counter subtraction public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { //The total is 6 CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i <= 6 ; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+" Go out"); countDownLatch.countDown(); //Number of threads per thread - 1 },String.valueOf(i)).start(); } countDownLatch.await(); //Wait for the counter to zero and execute down System.out.println("close door"); } }
Main methods:
-
countDown minus one;
-
await waits for the counter to return to zero.
await waits until the counter is 0, wakes up, and then continues to run down.
8.2,CyclickBarrier
In fact, it is an addition counter;
public class CyclicBarrierDemo { public static void main(String[] args) { //Main thread CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{ System.out.println("Summon the Dragon~"); }); for (int i = 1; i <= 7; i++) { //Child thread int finalI = i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+" Collected the third {"+ finalI+"} Dragon Ball"); try { cyclicBarrier.await(); //Add count wait } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
8.3,Semaphore
Semaphore: semaphore
Parking space:
3 parking spaces and 6 vehicles:
public class SemaphoreDemo { public static void main(String[] args) { //There are 3 parking spaces Semaphore semaphore = new Semaphore(3); for (int i = 1; i <= 6; i++) { int finalI = i; new Thread(()->{ try { semaphore.acquire(); //obtain //Grab a parking space System.out.println(Thread.currentThread().getName()+" Grabbed the parking space{"+ finalI +"}"); TimeUnit.SECONDS.sleep(2); //Stop for 2s System.out.println(Thread.currentThread().getName()+" Leave the parking space"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release();//release } },String.valueOf(i)).start(); } } }
Principle:
semaphore.acquire() obtains the resource. If the resource has been used up, wait for the resource to be released before using it!
semaphore.release() releases the current semaphore by + 1, and then wakes up the waiting thread!
Function: mutually exclusive use of multiple shared resources! Concurrent flow restriction, control the maximum number of threads!
9. Blocking queue
9.1. What is a blocking queue
Blocking queue It is a queue that supports two additional operations. These two additional operations are: when the queue is empty, the thread that gets the element will wait for the queue to become non empty. When the queue is full, the thread that stores the element will wait for the queue to be available. Blocking queue is often used in the scenario of producers and consumers. Producers are the threads that add elements to the queue, and consumers take elements from the queue A thread blocking queue is a container where producers store elements, and consumers only get elements from the container.
BlockingQueue is a subclass of Collection. The following are common subclasses of BlockingQueue:
-
ArrayBlockingQueue
-
LinkedBlockingQueue
-
SynchronousQueue
Blocking queue provides four processing methods:
Method / treatment | Throw exception | No exception will be thrown and there is a return value | Blocking, waiting | Timeout wait |
---|---|---|---|---|
add to | add | offer | put | offer(timeNum, timeUnit) |
remove | remove | poll | take | poll(timeNum, timeUnit) |
Returns the first element of the queue | element | peek |
/** * Throw exception */ public static void test1(){ //The size of the queue that needs to be initialized ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.add("a")); System.out.println(blockingQueue.add("b")); System.out.println(blockingQueue.add("c")); //Throw exception: java.lang.IllegalStateException: Queue full // System.out.println(blockingQueue.add("d")); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); //If you remove one more //This will also cause java.util.NoSuchElementException to throw an exception System.out.println(blockingQueue.remove()); } ======================================================================================= /** * No exception is thrown and there is a return value */ public static void test2(){ ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.offer("a")); System.out.println(blockingQueue.offer("b")); System.out.println(blockingQueue.offer("c")); //Adding an element that cannot be added, using offer will only return false and will not throw an exception System.out.println(blockingQueue.offer("d")); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); //If there are no pop-up elements, it will only return null and will not throw an exception System.out.println(blockingQueue.poll()); } ======================================================================================= /** * Waiting has been blocked */ public static void test3() throws InterruptedException { ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); //Always blocked and will not return blockingQueue.put("a"); blockingQueue.put("b"); blockingQueue.put("c"); //If the queue is full, another element will be entered. In this case, the program will wait until when the queue has a position to enter again, and the program will not stop // blockingQueue.put("d"); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); //If we do it again, this situation will also wait, and the program will keep running and blocking System.out.println(blockingQueue.take()); } ======================================================================================= /** * Wait timeout blocking * In this case, it will also wait for the queue to have a location or a product, but the timeout will end */ public static void test4() throws InterruptedException { ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); blockingQueue.offer("a"); blockingQueue.offer("b"); blockingQueue.offer("c"); System.out.println("Start waiting"); blockingQueue.offer("d",2, TimeUnit.SECONDS); //The timeout is 2s. If it exceeds 2s, the wait will end System.out.println("End waiting"); System.out.println("===========Value=================="); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println("Start waiting"); blockingQueue.poll(2,TimeUnit.SECONDS); //We won't wait for more than two seconds System.out.println("End waiting"); }
9.2,ArrayBlockingQueue
ArrayBlockingQueue is the most typical bounded queue. It uses an array to store elements, uses ReentrantLock to achieve thread safety, and uses Condition to block and wake up threads
We need to specify its capacity when we create it, and we can't expand it later. We can also specify whether it is fair in the constructor. The code is as follows:
public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
9.3,LinkedBlockingQueue
From the naming, we can see that this is a BlockingQueue implemented internally with a linked list. If we do not specify its initial capacity, its capacity defaults to the maximum Integer.MAX_VALUE. Because this number is very large, about the 31st power of 2, it is usually impossible for us to put so much data, so LinkedBlockingQueue is also called unbounded queue, representing it There are few boundaries.
Other features:
- Similarly, ReentrantLock is also used to achieve thread safety, and Condition is used to block and wake up threads
- The fairness of ReentrantLock cannot be set. It is unfair by default
- You can also set a fixed size
The default parameterless constructor is as follows. The default maximum value is Integer.MAX_VALUE:
public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
9.4 synchronization queue
The biggest difference of SynchronousQueue is that its capacity is 0, so there is no place to temporarily store elements. As a result, every time data is fetched, it will be blocked until data is put in; similarly, every time data is put in, it will be blocked until a consumer comes to fetch it.
It should be noted that the capacity of the SynchronousQueue is not 1 but 0. Because the SynchronousQueue does not need to hold elements, all it does is direct handoff . because the SynchronousQueue will directly transfer elements from the producer to the consumer whenever it needs to be transferred, and there is no need to store them during this period, its efficiency is very high if it is used properly.
Why is its capacity 0? We can see several methods:
The peek method always returns null. The code is as follows:
public E peek() { return null; }
Because the meaning of the peek method is to get the header node, but the capacity of the SynchronousQueue is 0, there is no header node, and the peek method is meaningless, so it always returns null.
Similarly, the element method will always throw a NoSuchElementException exception, but the implementation of this method is in its parent class AbstractQueue
public E element() { E x = peek(); if (x != null) return x; else throw new NoSuchElementException(); }
The size method of SynchronousQueue always returns 0 because it has no internal capacity. The code is as follows:
public int size() { return 0; }
take() of SynchronousQueue uses lock lock to ensure thread safety.
public class SynchronousQueueDemo { public static void main(String[] args) { BlockingQueue<String> synchronousQueue = new SynchronousQueue<>(); //Study if it is judged that this is a synchronous queue //Use two processes // Put a process in // Take out a process new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+" Put 1"); synchronousQueue.put("1"); System.out.println(Thread.currentThread().getName()+" Put 2"); synchronousQueue.put("2"); System.out.println(Thread.currentThread().getName()+" Put 3"); synchronousQueue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } },"T1").start(); new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take()); // TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take()); // TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } },"T2").start(); } }
10. Thread pool (key)
10.1. What is thread pool
java.util.concurrent.Executors provides an implementation of the java.util.concurrent.Executor interface to create a thread pool. Multithreading technology mainly solves the problem of multiple thread execution in the processor unit. It can significantly reduce the idle time of the processor unit and increase the throughput of the processor unit.
The time required for the machine to complete a task is: thread creation time t1, task execution time t2, thread destruction time t3; If t1 + t3 is much larger than t2, thread pool can be used to improve server performance. Thread pool technology mainly focuses on the creation and destruction time of threads. The creation time and destruction time are arranged in the start and end time of the server program or some idle time periods respectively, so that there will be no overhead of creation time and destruction time when the server program processes customer requests.
10.2 three methods of thread pool
ExecutorService threadPool1 = Executors.newSingleThreadExecutor(); // A thread pool that can execute only one thread at a time
ExecutorService threadPool2 = Executors.newFixedThreadPool(3); // A thread pool that can execute fixed parameters at one time
ExecutorService threadPool3 = Executors.newCachedThreadPool(); // Thread pool that can change automatically
- A thread pool that can execute only one thread at a time
public class Demo01 { public static void main(String[] args) { ExecutorService threadPool = Executors.newSingleThreadExecutor();//Single thread //When the thread pool runs out, you must close the thread pool try { for (int i = 1; i <=100 ; i++) { //Creating threads from a thread pool threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+ " ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } /* pool-1-thread-1 Executed!!! pool-1-thread-1 Executed!!! pool-1-thread-1 Executed!!! pool-1-thread-1 Executed!!! pool-1-thread-1 Executed!!! */ }
- A thread pool that can execute fixed parameters at one time
public class Demo01 { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3);//Fixed 3 threads //When the thread pool runs out, you must close the thread pool try { for (int i = 1; i <=100 ; i++) { //Creating threads from a thread pool threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+ " ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } /* pool-1-thread-1 Executed!!! pool-1-thread-2 Executed!!! pool-1-thread-3 Executed!!! pool-1-thread-2 Executed!!! pool-1-thread-1 Executed!!! pool-1-thread-3 Executed!!! */ }
- Thread pool that can change automatically
public class Demo01 { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3);//Fixed 3 threads //When the thread pool runs out, you must close the thread pool try { for (int i = 1; i <=100 ; i++) { //Creating threads from a thread pool threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+ " ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } /* pool-1-thread-1 Executed!!! pool-1-thread-4 Executed!!! pool-1-thread-3 Executed!!! pool-1-thread-2 Executed!!! pool-1-thread-5 Executed!!! pool-1-thread-6 Executed!!! */ }
10.3 seven parameters of thread pool
For the source code analysis of the three methods:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
Essence: ThreadPoolExecutor is enabled for all three methods
public ThreadPoolExecutor(int corePoolSize, //Core thread pool size int maximumPoolSize, //Maximum thread pool size //CPU intensive and I/O intensive //Available CPU cores (Runtime.getRuntime().availableProcessors()) //The number of I/O consuming threads in the program can also be about twice to twice the maximum number of I/O. long keepAliveTime, //If no one calls it, it will be released TimeUnit unit, //Timeout unit BlockingQueue<Runnable> workQueue, //Blocking queue ThreadFactory threadFactory, //Thread factories generally do not need to move when creating threads RejectedExecutionHandler handler //Reject policy ) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
For example, we define a thread pool corePoolSize = 3, maximumPoolSize = 5, and the blocking queue size is 3. We use the case of the bank counter to explain the whole process, which can be divided into four stages;
- When there are no more than three people, the system operates normally and the business can be handled smoothly;
- When the number of visitors exceeds 3 but not more than 6, the whole system can also operate normally, but at this time, people waiting for business in the bank's BlockingQueue < > (3));
- When the number of visitors exceeds 6 but not more than 8(8 = maximumPoolSize + blocking queue size), the system will change. The bank will reopen some unused counters, which will be used by people in total. If there is no one at the counter within the keepAliveTime period after opening, the counter will be closed again;
- When the number of people from the bank exceeds 8, the system can't bear the traffic, and the system will collapse. At this time, a rejection strategy is needed to reject the people who are full;
10.4. Four rejection strategies of threads
-
The first rejection policy: new ThreadPoolExecutor.AbortPolicy(). If the thread is full, the new process will not be processed and an exception will be thrown;
static void demo1(){ ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 3, 5, 2, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); /* When totalThread reaches 3, the initial counter is just enough for processing; When totalThread reaches 6, the rest area (blocking queue) is full, When totalThread reaches 8, all additional counters are opened, When the totalThread exceeds 8, if the new thread cannot handle it, an exception java.util.concurrent.RejectedExecutionException will be thrown */ int totalThread = 8; try { for (int i = 0; i < totalThread; i++) { threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"Executed!!!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }
-
The second processing strategy: new ThreadPoolExecutor.CallerRunsPolicy(), when the thread pool is full, no exception will be thrown if there are new ones coming and going;
static void demo2(){ ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 3, 5, 2, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());//If it is excessive, it will be handed over to the main thread int totalThread = 12; try { for (int i = 0; i < totalThread; i++) { threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"Executed!!!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } /* pool-1-thread-2 Executed!!! pool-1-thread-4 Executed!!! main Executed!!! pool-1-thread-1 Executed!!! pool-1-thread-3 Executed!!! pool-1-thread-1 Executed!!! pool-1-thread-4 Executed!!! main Executed!!! pool-1-thread-5 Executed!!! pool-1-thread-2 Executed!!! pool-1-thread-1 Executed!!! pool-1-thread-3 Executed!!! */ }
-
The third processing strategy: new ThreadPoolExecutor.DiscardPolicy(), when the thread pool is full, the thread will be lost and no exception will be thrown
static void demo3(){ ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 3, 5, 2, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());//If it is excessive, it will be handed over to the main thread int totalThread = 15; try { for (int i = 0; i < totalThread; i++) { threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"Executed!!!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }
-
The fourth processing strategy: new ThreadPoolExecutor. Discardolddestpolicy(), when the thread pool is full, it will try to compete with the earliest thread. If it succeeds, it will join, otherwise it will be discarded and no exception will be thrown
static void demo4(){ ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 3, 5, 2, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());//If it is excessive, it will be handed over to the main thread int totalThread = 14; try { for (int i = 0; i < totalThread; i++) { threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"Executed!!!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }
11. Four functional interfaces (must be mastered)
Functional interface: an interface with only one abstract method
11.1 Function interface
@FunctionalInterface public interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t)
Pass in parameter T and return type R
give an example:
/** * Function Functional interface */ public class Demo01 { public static void main(String[] args) { Function<String,String> function = (str) ->{return str;}; System.out.println(function.apply("starasdas")); } //Print out what you enter. }
11.2 predict type interface
@FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t);
There is one input parameter, and the return value can only be Boolean.
give an example:
public class Demo2 { public static void main(String[] args) { //Determine whether the string is empty Predicate<String> predicate = (str)->{return str.isEmpty();}; System.out.println(predicate.test("11")); System.out.println(predicate.test("")); } }
11.3 Consumer interface
@FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t);
There is an input parameter with no return value. (no return value is required for consumer consumption)
give an example:
public class Demo3 { public static void main(String[] args) { Consumer<String> consumer = (str)->{ System.out.println(str); }; consumer.accept("abc"); } }
11.4 Supplier supply interface
@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
There are no parameters, only return values.
give an example:
public class Demo4 { public static void main(String[] args) { Supplier<String> supplier = ()->{return "1024";}; System.out.println(supplier.get()); } }
12. Stream flow calculation
What is Stream streaming?
Storage + calculation
Collection and Mysql are essentially used to store things.
All calculations should be left to the flow.
Example:
/** * There are 5 users, filtering: * 1. ID Even * 2. Older than 23 * 3. User name to uppercase * 4. User name alphabetical inversion * 5. Output only one user */ public class Test { public static void main(String[] args) { User u1 = new User(1, "a", 21); User u2 = new User(2, "b", 22); User u3 = new User(3, "c", 23); User u4 = new User(4, "d", 24); User u5 = new User(5, "e", 25); // Collection for storage List<User> list = Arrays.asList(u1, u2, u3, u4, u5); //The calculation is given to the Stream //Chain programming!!!! // Filter: filter // map: conversion // Sorted: sorted // limit: paging list.stream() .filter(u -> {return u.getId()%2 == 0;}) .filter(u -> {return u.getAge() > 23}) .map(u -> {return u.getName().toUpperCase();}) .sorted((uu1, uu2) -> {return uu1.compareTo(uu2);}) .limit(1) .forEach(System.out::println); } }
13,ForkJoin
13.1 what is ForkJoin
Starting from JDK1.7, Java provides Fork/Join framework for parallel task execution, which is to divide a large task into several small tasks, and finally summarize the results of each small task to obtain the results of this large task.
There are two main steps in the process:
-
Task cutting
-
Result merging
13.2 ForkJoin features
Work stealing: a thread steals tasks from other queues to execute.
A worker thread will maintain a = = double ended queue = =, which contains multiple subtasks. For each worker thread, tasks will be executed from head to tail. At this time, some threads will execute faster and consume all tasks quickly.
Thread task stealing: for example, when you eat fruit with your little partner, your share is finished, but his share is not finished. At this time, you secretly eat some of his fruit. There are two sub threads executing two tasks, A and B workqueues executing tasks. When A's task is finished and B's task is not finished, A's WorkQueue is from forkjo of B's WorkQueue The intask array takes away part of the tail tasks to execute, which can reasonably improve the operation and calculation efficiency.
13.2. How to use ForkJoin
- Execute through ForkJoinPool
- Compute task execute (forkjointask <? > task)
- The calculation class should inherit ForkJoinTask (ForkJoinTask represents the task running in ForkJoinPool).
Main methods:
-
fork() arranges an asynchronous execution in the thread pool where the current thread is running. The simple understanding is to create another subtask.
-
join() returns the calculation result when the task is completed.
-
invoke() starts the task and waits for the calculation to complete if necessary.
Subclass: Recursive
- RecursiveAction is a ForkJoinTask that recursively has no result (no return value)
- Recursive task a ForkJoinTask with recursive results (with return value)
Code example:
Core code
package cn.guardwhy.forkJoin; import java.util.concurrent.RecursiveTask; /* * The task of summation calculation!! * 1.Using forkjoin * 1.1forkjoinpool Execute through it. * 1.2 Calculate the task forkjoinpool.extract (forkjointask task) * 1.3 The calculation class should inherit ForkJoinTask */ public class ForkJoinDemo extends RecursiveTask<Long> { private Long start; // Starting value private Long end; // End value // critical value private Long temp = 10000L; public ForkJoinDemo(Long start, Long end) { this.start = start; this.end = end; } // computing method @Override protected Long compute() { // Conventional mode if((end - start) < temp){ // Define sum value Long sum = 0L; for (Long i = start; i <= end ; i++) { sum += i; } return sum; }else { // ForkJoin recursion long middle = (start + end) / 2; // Intermediate value ForkJoinDemo task1 = new ForkJoinDemo(start, middle); // Split the task and push the task into the thread queue task1.fork(); ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end); // Split the task and push the task into the thread queue task2.fork(); return task1.join() + task2.join(); } } }
Test code:
package cn.guardwhy.forkJoin; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.stream.LongStream; public class ForkJoinTest { public static void main(String[] args) throws ExecutionException, InterruptedException { // Test1(); / / sum = 500000000500000000 time: 6235 test2(); // sum=500000000500000000 time: 6017 // Test03(); / / sum = 500000000500000000 time: 102 } // 1. Common mode public static void test1() { Long sum = 0L; long start = System.currentTimeMillis(); for (Long i = 1L; i <=10_0000_0000 ; i++) { sum += i; } Long end = System.currentTimeMillis(); System.out.println("sum=" +sum+" time: " + (end-start)); } // 2. Use the ForkJoin method private static void test2() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L); // Submit task ForkJoinTask<Long> submit = forkJoinPool.submit(task); Long sum = submit.get(); long end = System.currentTimeMillis(); System.out.println("sum=" +sum+" time: " + (end-start)); } // 3. Chain programming private static void test03() { long start = System.currentTimeMillis(); // Stream parallel stream long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum); long end = System.currentTimeMillis(); System.out.println("sum=" +sum+" time: " + (end-start)); } }
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-3MSsN95O-1632053112977)(C:\Users \ dad \ appdata \ roaming \ typora \ typora user images \ image-20210917234608412. PNG)]
14. Asynchronous callback
14.1 Future interface
The original design intention of Future: to model the results that will happen at some time in the Future.
The Future interface models an asynchronous calculation that returns a reference to the result of the operation. When the operation is completed, the reference is returned to the caller. Trigger those potentially time-consuming operations in Future to liberate the calling thread so that it can continue to perform other valuable work without waiting for the time-consuming operation to complete.
In fact, the front end -- > sends ajax asynchronous requests to the back end.
Example: use Future to perform a time-consuming operation asynchronously:
ExecutorService executor = Executors.newCachedThreadPool(); Future<Double> future = executor.submit(new Callable<Double>() { //Submit a Callable object to ExecutorService public Double call() { return doSomeLongComputation();//Perform time-consuming operations asynchronously in a new thread } }); doSomethingElse(); try { Double result = future.get(1, TimeUnit.SECONDS);//Get the asynchronous operation result. If it is blocked and cannot get the result, wait for 1 second and exit } catch (ExecutionException ee) { // The calculation threw an exception } catch (InterruptedException ie) { // The current thread was interrupted while waiting } catch (TimeoutException te) { // Timed out before the Future object completed }
This programming method allows your thread to perform some other tasks while ExecutorService concurrently calls another thread to perform time-consuming operations. If it cannot continue without the results of asynchronous operations, you can call its get method to obtain the operation results. If the operation has been completed, this method will immediately return the operation results. No It blocks the thread until the operation is completed and the corresponding result is returned.
In order to deal with the possibility that long-running operations will never return, although Future provides a get method without any parameters, it is recommended to use the overloaded version of get method. It accepts a timeout parameter and can define the time for the thread to wait for Future results, rather than endless waiting.
14.2,CompletableFuture
**The completabilefuture * * class provides a large number of sophisticated factory methods that make it easier to complete the whole process without worrying about implementation details.
-
runAsync asynchronous callback with no return value
public class CompletableFutureTest { public static void main(String[] args) throws Exception { CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "runAsync=>Void"); }); System.out.println("1111"); completableFuture.get(); } }
-
supplyAsync asynchronous callback with return value
public class CompletableFutureTest { public static void main(String[] args) throws Exception { CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer"); int i = 10 / 0; return 1024; }); System.out.println(completableFuture.whenComplete((t, u) -> { System.out.println("t=>" + t); // Normal return result System.out.println("u=>" + u); // Error message: java.util.concurrent.CompletionException:java.lang.ArithmeticException: /byzero }).exceptionally((e) -> { //error callback System.out.println(e.getMessage()); return 404; // You can get the return result of the error }).get()); } }
If an exception occurs, get can get the value returned by exceptionally;
15,JMM
15.1 what is JMM
JMMJMM is the java memory model. It is not an actual thing, but an artificially formed convention. It is a concept. The java memory model is to shield the differences between the system and hardware so that a set of code can achieve the same access results on different platforms.
Some synchronization conventions of JMM:
- Before unlocking the thread, the value stored in the working memory of the thread must be immediately refreshed to the shared variable in the main memory!
- Before locking, the thread must read the latest value in main memory into working memory!
- Locking and unlocking are the same lock!
The data operated in the thread should be read from the main memory and backed up to the thread's own working memory. As a copy, the main memory will not actively update the data to the thread.
15.2. 8 kinds of memory interaction of threads
- lock: a variable that acts on main memory and identifies a variable as thread exclusive
- unlock: a variable that acts on the main memory and releases a locked shared variable
- read: a variable that acts on the main memory and transfers the value of a variable from the main memory to the working memory of the thread
- load: a variable that acts on the working memory and puts the variable value obtained through the read operation into the working memory
- use: a variable that acts on the working memory and transfers the variable in the working memory to the execution engine. This instruction will be used whenever the virtual machine encounters the value of the variable that needs to be used
- assign: a variable that acts on the working memory and puts the value transmitted by the execution engine into the working memory
- store: a variable that acts on the main memory. It transfers the value of a variable from the working memory in the thread to the main memory for subsequent write operations
- write: a variable that acts on the main memory and puts the variable value obtained by the store operation from the working memory into the main memory
15.3 JMM imposes the following constraints on the above eight memory operation instructions
- read and load, user and assign, store and write, lock and unlock must appear in pairs. It is not allowed to operate one of the instructions alone
- The thread is not allowed to discard the assign operation closest to it, that is, after the variable value in the working memory changes, it must inform the main memory
- A thread is not allowed to synchronize data that has not been assign ed from the working memory to the main memory
- A new variable must be generated in the main memory. The working memory is not allowed to initialize a variable as a shared variable without permission, that is, the load and assign operations must be performed before the use and store operations are implemented
- Only one thread is allowed to lock the same variable at the same time; After multiple locks, you must perform the same number of unlocks to unlock it
- If you lock a variable, the value of this variable in all working memory will be cleared, that is, the thread that obtains the lock each time must re read the variable value in the main memory before locking, and then submit it to the execution engine for use operation
- If a variable is not locked, you cannot unlock it, nor can you unlock a variable locked by other threads
- Before locking a variable, the value of the variable in the working memory must be synchronized back to the main memory
But there is a problem:
Suppose there is a main thread and an ordinary thread. The operation performed by the ordinary thread is: when num is 0, it will loop all the time; At this time, the main thread assigns a value of 1 to num. ordinary threads do not know that num has been modified, and the program will always execute and will not stop!
public class VolatileDemo { private static int num = 0; public static void main(String[] args) { new Thread(()->{ // Thread 1 while (num == 0) { } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } num = 1; System.out.println(num); } }
Solution: volatile keyword, see the next section.
16,Volatile
16.1 what is Volatile
Volatile is a lightweight synchronization mechanism provided by Java virtual machine (compared with synchronized)
- Ensure visibility = > after the shared variable in JMM main memory is modified, all threads will be notified to back up to their respective working memory
- Atomicity is not guaranteed
- Prohibit instruction rearrangement
16.2 guaranteed variability
public class JMMDemo01 { // If volatile is not added, the program will loop // Adding volatile ensures visibility private volatile static Integer number = 0; public static void main(String[] args) { //main thread //Child thread 1 new Thread(()->{ while (number==0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //Child thread 2 new Thread(()->{ while (number==0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } number=1; System.out.println(number); } }
As long as the volatile variable is added, notify the main thread in time that the number variable has changed, and copy the main memory in the main thread to the working memory.
16.3 atomicity is not guaranteed
Atomicity: that is, one or more operations are either all executed and the execution process will not be interrupted by any factor, or they will not be executed at all. live and die together
First code:
class MyData{ volatile int number = 0; public void changeData(){ number ++; } } /** * When a thread reads and assigns a variable, it must first copy the variable from the main memory to its own working memory space, operate in the working memory, and then write the variable back to the main memory after the operation is completed */ public class Demo { //main thread, program entry public static void main(String[] args) { //Create an object with number 0 in main memory MyData myData = new MyData(); for (int i = 1; i <= 20; i++) { //Create 20 threads new Thread(()->{ //A thread performs 1000 plus one operations for (int j = 1; j <= 1000; j++) { myData.changeData(); } },String.valueOf(i)).start(); } //If the program is not closed, it will continue to execute the main thread and GC thread. It is judged that the number of threads is greater than two, indicating that there are still threads that have not completed the task. Continue to execute the above code while (Thread.activeCount() > 2){ Thread.yield(); } //Ideally, the number of numbers is 20 * 1000 = 20000, while volatile does not guarantee atomicity. In fact, the value of number printed is generally not 20000 System.out.println(Thread.currentThread().getName()+"\t Print number Quantity of:" + myData.number); } }
This shows that volatile is unsafe to use threads.
So why doesn't volatile guarantee atomicity?
For example, when thread 1, thread 2 and thread 3 get number=0 in the main memory at the same time and add one hour in the working memory, the variables in the working memory of each thread will be written back to the main memory. During the process of writing back to the main memory, thread 1 is suspended because the cpu thread resources are preempted. If it does not come, notify other threads that the value of number has been changed to 1, Thread 2 adds one to the variable number=0 (the variable number=1 has not been updated) copied from the main memory (ideally, number=2 will be written back to the main memory at this time), but the actual situation is that the variable number=1 in the working memory of thread 2 is written back to the main memory, overwriting the number=1 written back to the main memory in thread 1, resulting in the loss of values.
First, explain the command number + +
Mydata. Java - > mydata. Class - > JVM bytecode
number + + this command is split into three instructions in the JVM bytecode:
Execute getfield to get the original value
Execute iadd to add one
Execute putfield to write the accumulated value back to main memory
In javap -c is the java bytecode, which is the original command at the bottom of assembly
volatile does not guarantee atomicity. How to solve atomicity?
- Add synchronized, but affect concurrency
class MyData{ volatile int number = 0; public synchronized void changeData(){ number++;//add one-tenth } } /** * When a thread reads and assigns a variable, it must first copy the variable from the main memory to its own working memory space, operate in the working memory, and then write the variable back to the main memory after the operation is completed */ public class Demo { //main thread, program entry public static void main(String[] args) { //Create an object with number 0 in main memory MyData myData = new MyData(); for (int i = 1; i <= 20; i++) { //Create 20 threads new Thread(()->{ //A thread performs 1000 plus one operations for (int j = 1; j <= 1000; j++) { myData.changeData(); } },String.valueOf(i)).start(); } //If the program is not closed, it will continue to execute the main thread and GC thread. Judge that the number of threads is greater than two and continue to execute the above code, while (Thread.activeCount() > 2){ Thread.yield(); } //Ideally, the number of numbers is 20 * 1000 = 20000, while volatile does not guarantee atomicity. In fact, the number of printed numbers is generally not 20000 System.out.println(Thread.currentThread().getName()+"\t Print number Quantity of:" + myData.number); } }
- Atomic integer using AtomicInteger
class MyData{ volatile int number = 0; AtomicInteger atomicInteger = new AtomicInteger(); public void changeData(){ atomicInteger.getAndIncrement();//add one-tenth } } /** * When a thread reads and assigns a variable, it must first copy the variable from the main memory to its own working memory space, operate in the working memory, and then write the variable back to the main memory after the operation is completed */ public class Demo { //main thread, program entry public static void main(String[] args) { //Create an object with number 0 in main memory MyData myData = new MyData(); for (int i = 1; i <= 20; i++) { //Create 20 threads new Thread(()->{ //A thread performs 1000 plus one operations for (int j = 1; j <= 1000; j++) { myData.changeData(); } },String.valueOf(i)).start(); } //If the program is not closed, it will continue to execute the main thread and GC thread. Judge that the number of threads is greater than two and continue to execute the above code, while (Thread.activeCount() > 2){ Thread.yield(); } //Ideally, the number of numbers is 20 * 1000 = 20000, while volatile does not guarantee atomicity. In fact, the number of printed numbers is generally not 20000 System.out.println(Thread.currentThread().getName()+"\t Print number Quantity of:" + myData.atomicInteger); } }
16.4 prohibition of instruction rearrangement
Here we introduce a concept:
What is the order of JMM?
How to understand data dependency? It's your father before you.
16.1 what is instruction rearrangement
Suppose I write the 20th line of code, which may not be executed from the first line to the 20th line. For example, if you take part in the college entrance examination and write the questions given by the questioner, you may not be able to do the last one from the first one. You may write what you will first and other difficult questions last.
Code case 1:
In the multithreaded environment, there are three sequences of statement execution: 1234, 2134 and 1324. Statement 4 cannot be rearranged and become the first one. The reason is that the data dependency of what is said. Variables must be declared before use.
Code case 2:
The normal result is x=0, y=0; But if an instruction rearrangement occurs,
It may appear in thread A. execute b=1 first, and then x=a;
It may occur in thread B. first execute a=2, and then execute y=b;
Then the possible results are as follows: x=2; y=1.
Code case 3:
In a multithreaded environment, instruction rearrangement will lead to two results: one is 0 + 5 = 5 and the other is 1 + 5 = 6
In the normal single thread environment, statement 1, statement 2 and statement 3 will be executed, and the result will be printed as 5
In the multithreaded environment, the instructions are rearranged. First execute statement 2, then execute statement 3, and finally execute statement 1. The result is printed as 6
It's terrible. Well, the consistency of data cannot be guaranteed, so volatile needs to prohibit instruction rearrangement.
volatile prohibit instruction rearrangement summary
So in practical applications, where is the memory barrier most used? Singleton mode
17. Singleton mode
Singleton mode may have security problems in multi-threaded environment
Singleton mode in single thread:
class MyData{ private static MyData myData = null; private MyData(){ System.out.println(Thread.currentThread().getName()+"\t Construction method"); } public static MyData getInstance(){ if(myData == null){ myData = new MyData(); } return myData; } } public class Demo { //main thread, program entry public static void main(String[] args) { System.out.println(MyData.getInstance() == MyData.getInstance()); System.out.println(MyData.getInstance() == MyData.getInstance()); } }
Console printing is correct:
Singleton mode under multithreading:
public class Demo { private static Demo demo = null; private Demo(){ System.out.println(Thread.currentThread().getName()+"\t Construction method"); } public static Demo getInstance(){ if(demo == null){ demo = new Demo(); } return demo; } //main thread, program entry public static void main(String[] args) { for (int i = 1; i <= 10; i++) { new Thread(()->{ Demo.getInstance(); },String.valueOf(i)).start(); } } }
The console prints an error:
It should have been printed once. As a result, 10 threads printed 5 times, which is a problem.
Some people would say add a synchronized:
public class Demo { private static Demo demo = null; private void Demo(){ System.out.println(Thread.currentThread().getName()+"\t Construction method"); } public static synchronized Demo getInstance(){ if(demo == null){ demo = new Demo(); } return demo; } //main thread, program entry public static void main(String[] args) { for (int i = 1; i <= 10; i++) { new Thread(()->{ Demo.getInstance(); },String.valueOf(i)).start(); } } }
Console printing is correct:
But this is not good. synchronized is of the weight type, and data consistency is guaranteed, but concurrency is affected
How to solve this problem?
At this time, we will first introduce the DCL pun detection lock mechanism, and then explain it a little later.
17.1 DCL double off lock mechanism
Make a judgment before and after locking.
public class Demo { private static Demo demo = null; private Demo(){ System.out.println(Thread.currentThread().getName()+"\t Construction method"); } public static Demo getInstance(){ if(demo == null){ synchronized (Demo.class){ if(demo == null){ demo = new Demo(); } } } return demo; } //main thread, program entry public static void main(String[] args) { for (int i = 1; i <= 10; i++) { new Thread(()->{ Demo.getInstance(); },String.valueOf(i)).start(); } } }
Why add two layers of judgment? Stronger
For example: when you go to the bathroom, you confirm that there is no one, then go in, insert the door, push the door again, and do what you should do after confirming that it is safe.
Is it 100% OK to use DCL (pun lock checking mechanism) in a multithreaded environment?
No, DCL (double pass lock checking mechanism) is not necessarily thread safe. In a multithreaded environment, the orderliness in JMM will rearrange instructions and change the execution order, which can not guarantee 100%. Adding volatile prevents instruction rearrangement.
Single case mode volatile analysis
In a multithreaded environment, when a thread accesses an instance that is not null, the instance may not have been initialized, resulting in thread safety problems.
So adding volatile can guarantee 100% ok.
public class Demo { private static volatile Demo demo = null; private Demo(){ System.out.println(Thread.currentThread().getName()+"\t Construction method"); } public static Demo getInstance(){ if(demo == null){ synchronized (Demo.class){ if(demo == null){ demo = new Demo(); } } } return demo; } //main thread, program entry public static void main(String[] args) { for (int i = 1; i <= 10; i++) { new Thread(()->{ Demo.getInstance(); },String.valueOf(i)).start(); } } }
17.2 hungry Han style
public class Hungry { /** * The first time you come in, you will create an instance * It may waste space */ private byte[] data1=new byte[1024*1024]; private byte[] data2=new byte[1024*1024]; private byte[] data3=new byte[1024*1024]; private byte[] data4=new byte[1024*1024]; //Construction method privatization private Hungry(){ } private final static Hungry hungry = new Hungry(); public static Hungry getInstance(){ return hungry; } }
17.3 lazy type
//Lazy style public class Lazy { //The lazy man is the source of the name private static Lazy lazy = null; //Construction method privatization private Lazy(){} //Provides a method to get an instance public static synchronized Lazy getInstance(){ //Use synchronized to ensure thread safety if(lazy == null){ lazy = new Lazy(); } return lazy; } }
17.4 static internal class
public class Holder { //Construction method privatization private Holder(){} //In fact, it returns the instance object of the external class through a static private internal class private static class InnerSingleton{ private static final Holder holder = new Holder(); } //Provides a public method for obtaining an external instance public static final Holder getInstance(){ return InnerSingleton.holder; } }
17.5 enumeration type
//What is enum? Enum itself is a Class class public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>() EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
Summary:
- Lazy type: lazy type is a typical time for space, that is, every time you get an instance, you will judge whether you need to create an instance and waste judgment time. Of course, if no one has been using it, the instance will not be created, which saves memory space.
- Hungry Han style: space for time. This instance will be created whether it will be used or not.
- Double check lock mode: fully combines the advantages of lazy and hungry, which not only saves space, but also improves efficiency
- Static internal class: the characteristics of static internal class ensure that our JVM will initialize singleton instance only when calling getInstance() method. Therefore, this method of creating singleton also solves the performance problems caused by thread safety and starvation, and there is no need to lock. It is a relatively recommended method of creating singleton.
- Enumeration method: enumeration method is a rare implementation method, but it is more concise and clear when looking at the code implementation. And she also automatically supports serialization mechanism.
18. Deep understanding of CAS
18.1 what is CAS
CAS is the abbreviation of CompareAndSwap, which is used for comparison and exchange.
When a thread reads and assigns a variable, it must first copy the variable from the main memory to its thread's working memory space, operate in the working memory, and write the variable back to the main memory after the operation is completed.
public class Demo { //main thread, program entry public static void main(String[] args) { //Atomic class integer. This object is created in main memory. The initial value is 5, and the default initial value is 0 AtomicInteger atomicInteger = new AtomicInteger(5); //When the working memory in the thread is to be written back to the main memory, take the first parameter as the expected value and compare it with the value in the main memory. If the expected value is the same as the value in the main memory, it is 5, and the updated value is 2019 System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t : "+ atomicInteger.get()); //Thread a new Thread(()->{ //Now the value of the object in the main memory is 2019, and the expected value is 5. It is different from the value of the object in the main memory. 1024 cannot be written back to the main memory System.out.println(atomicInteger.compareAndSet(5, 1024)+"\t : "+ atomicInteger.get()); },"a").start(); } }
Console:
That is why AtomicInteger can solve atomicity, because it will have a comparison and interaction when writing back to main memory.
AtomicInteger atomicInteger = new AtomicInteger();//The default initial value is 0 atomicInteger.getAndIncrement();//Operation of adding one
View the source code of the called getAndIncrement() method:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { //Obtain the memory address or memory offset through the objectFieldOffset method valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; public final int getAndIncrement() { //this is the current object, valueOffset is the memory address, and 1 is the write dead value return unsafe.getAndAddInt(this, valueOffset, 1); }
You can see that the method body calls the getAndAddInt() method in the Unsafe class.
Source code analysis:
//Get the address value of the current object. var1 is the memory address of the object and var2 is the memory address of the object. It is equivalent to that the current thread first copies the variable value from the main memory to its own working memory. var5 is the variable value in the working memory var5 = this.getIntVolatile(var1, var2) //The calling CAS method type is Int. if the value of the address of the current object (var1) (the value of var2) is the same as the expected value (var5), the updated value (var5+var4) is written back to the main memory this.compareAndSwapInt(var1, var2, var5, var5 + var4) //Only when it is successful can it be written back
In fact, it uses spin lock.
Next, you can learn more about the CompareAndSwapInt method:
The Unsafe class is under the rt.jar package in the jdk
Summary:
18.2 disadvantages of CAS
First disadvantage:
Second disadvantage:
Third disadvantage:
19. AtomicReference atomic reference
It was explained that volatile does not guarantee atomicity. In order to solve the atomicity problem, AtomicInteger atomic integer is used to solve the atomicity problem of basic type operation. Should our custom entity class or basic data type ensure atomicity? That is to use AtomicReference atomic reference.
AtomicReference atomic reference code:
class User{ String userName; int age; public User(String userName, int age) { this.userName = userName; this.age = age; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", age=" + age + '}'; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class Demo { //main thread, program entry public static void main(String[] args) { User user1 = new User("java_wxid",25); User user2 = new User("javaliao",22); AtomicReference<User> atomicReference = new AtomicReference<>(); atomicReference.set(user1); System.out.println(atomicReference.compareAndSet(user1, user2)+"\t"+atomicReference.get().toString()); new Thread(()->{ System.out.println(atomicReference.compareAndSet(user1, user1)+"\t"+atomicReference.get().toString()); },"a").start(); } }
Console:
But this can not solve the ABA problem of CAS
ABA problem code:
public class Demo { static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); public static void main(String[] args) { new Thread(()->{ //Perform ABA operation atomicReference.compareAndSet(100,101); atomicReference.compareAndSet(101,100); },"t1").start(); new Thread(()->{ try { //Pause for one second to ensure that t1 thread completes an ABA operation Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019)); System.out.println(atomicReference.get()); },"t2").start(); } }
There must be something fishy in the middle, so the solution is provided:
Atomic reference using AtomicStampedReference version number.
As long as the version number of T1 is weaker than that of T2, it needs to be updated. Assuming that the value of the second version number of thread T1 is 2019 and thread T2 has been modified twice and the version number is 3, the version number of thread T2 is 2 cannot be compared and exchanged. It is necessary to copy and update the value of the version number of thread T3 again before operation.
public class Demo { static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1); public static void main(String[] args) { System.out.println("===============solve ABA Problem scheme==============="); new Thread(()->{ //Get version number int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"First version number:"+stamp+"\t Current actual latest value:"+atomicStampedReference.getReference()); try { //Pause for one second Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t Second version number:"+atomicStampedReference.getStamp()+"\t Current actual latest value:"+atomicStampedReference.getReference()); atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t Third version number:"+atomicStampedReference.getStamp()+"\t Current actual latest value:"+atomicStampedReference.getReference()); },"t3").start(); new Thread(()->{ //Get version number int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t First version number:"+stamp); try { //Pause for one second to ensure that t3 thread completes an ABA operation Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t Latest version number:"+atomicStampedReference.getStamp()+"\t current t4 The version number of is:"+stamp); System.out.println(Thread.currentThread().getName()+"\t Only the latest version number and t4 The main memory can be written back only when the version numbers of are consistent. Whether the write back is successful:"+ atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1)); System.out.println(Thread.currentThread().getName()+"\t Current actual latest value:"+atomicStampedReference.getReference()); },"t4").start(); } }
Console:
At this time, t4 thread can update the value of 100 with version number 3, which solves the problem that CAS only cares about the results regardless of the process.
w Thread(()->{
System.out.println(atomicReference.compareAndSet(user1, user1)+"\t"+atomicReference.get().toString());
},"a").start();
}
}
Console: [External chain picture transfer...(img-FpNAJbMH-1632053112992)] But it can't be solved CAS of ABA problem ABA Problem code: ```java public class Demo { static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); public static void main(String[] args) { new Thread(()->{ //Perform ABA operation atomicReference.compareAndSet(100,101); atomicReference.compareAndSet(101,100); },"t1").start(); new Thread(()->{ try { //Pause for one second to ensure that t1 thread completes an ABA operation Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019)); System.out.println(atomicReference.get()); },"t2").start(); } }
[external chain picture transferring... (img-xlzermfs-16320531293)]
There must be something fishy in the middle, so the solution is provided:
Atomic reference using AtomicStampedReference version number.
[external chain pictures are being transferred... (img-fzsitg4o-16320531293)]
As long as the version number of T1 is weaker than that of T2, it needs to be updated. Assuming that the value of the second version number of thread T1 is 2019 and thread T2 has been modified twice and the version number is 3, the version number of thread T2 is 2 cannot be compared and exchanged. It is necessary to copy and update the value of the version number of thread T3 again before operation.
public class Demo { static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1); public static void main(String[] args) { System.out.println("===============solve ABA Problem scheme==============="); new Thread(()->{ //Get version number int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"First version number:"+stamp+"\t Current actual latest value:"+atomicStampedReference.getReference()); try { //Pause for one second Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t Second version number:"+atomicStampedReference.getStamp()+"\t Current actual latest value:"+atomicStampedReference.getReference()); atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t Third version number:"+atomicStampedReference.getStamp()+"\t Current actual latest value:"+atomicStampedReference.getReference()); },"t3").start(); new Thread(()->{ //Get version number int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t First version number:"+stamp); try { //Pause for one second to ensure that t3 thread completes an ABA operation Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t Latest version number:"+atomicStampedReference.getStamp()+"\t current t4 The version number of is:"+stamp); System.out.println(Thread.currentThread().getName()+"\t Only the latest version number and t4 The main memory can be written back only when the version numbers of are consistent. Whether the write back is successful:"+ atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1)); System.out.println(Thread.currentThread().getName()+"\t Current actual latest value:"+atomicStampedReference.getReference()); },"t4").start(); } }
Console:
[external chain picture transferring... (img-mUbB2BUA-1632053112993)]
At this time, t4 thread can update the value of 100 with version number 3, which solves the problem that CAS only cares about the results regardless of the process.