1JUC concurrent programming
java.util Toolkit
The common Thread code Thread is inefficient
Runnable has no return value and its efficiency is lower than that of Callable!
2 threads and processes
Threads and processes are not solid if they cannot be expressed in one sentence!
1 process
A program, QQ exe Music. Collection of exe programs;
A process contains multiple threads, including at least one!
How many threads does java have by default? Two -- main thread and GC thread
Thread: open a process Typora, which can write (thread) and save automatically (thread is responsible)
For java: Thread Runnable Callable
**Can Java really start threads** It can't be opened. Java doesn't have permission to start threads and operate hardware. This is a native local method that calls the underlying C + + code.
Concurrent, parallel?
2 concurrent
Multiple threads operate on the same resource.
The CPU has only one core, and multiple threads are simulated. The world's martial arts are only fast. Then we can use CPU fast alternation to simulate multithreading.
**The essence of concurrent programming: * * make full use of CPU resources!
3 parallel
Parallel: multiple people walking together
The CPU is multi-core, and multiple threads can be executed at the same time. We can use thread pool!
CPU acquisition method:
public class Test { public static void main(String[] args) { System.out.println(Runtime.getRuntime().availableProcessors()); } }
4 status of threads
public enum State { //newborn NEW, //function RUNNABLE, //block BLOCKED, //Wait, die, etc WAITING, //Timeout wait TIMED_WAITING, //termination TERMINATED; }
5wait sleep difference
1. From different classes
wait => Object
sleep => Thread
Generally, dormancy is used in Enterprises:
TimeUnit.DAYS.sleep(1); //Dormancy for 1 day TimeUnit.SECONDS.sleep(1); //Sleep for 1s
2. About lock release
wait will release the lock;
sleep sleeps and won't release the lock;
3. The scope of use is different
wait must be in the synchronization code block;
Sleep can sleep anywhere;
4. Need to catch exceptions
wait is an exception that does not need to be caught;
sleep must catch exceptions;
3lock (key)
1 traditional synchronized
public class Test { public static void main(String[] args) { final Ticket ticket = new Ticket(); 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 OOP attributes and methods class Ticket { private int number = 30; //How to sell tickets public synchronized void sale() { if (number > 0) { System.out.println(Thread.currentThread().getName() + "Sold the second" + (number--) + "Ticket remaining" + number + "Ticket"); } } }
2Lock
Fair lock: very fair, must come first ~;
Unfair lock: very unfair, you can jump the queue; (non fair lock by default)
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Description: * * @author jiaoqianjin * Date: 2020/8/10 22:05 **/ public class Test { public static void main(String[] args) { final Ticket2 ticket = new Ticket2(); 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(); } } //lock Trilogy //1, Lock lock=new ReentrantLock(); //2, lock. Lock //3. Finally = > unlock: lock unlock(); class Ticket2 { private int number = 30; // Create lock Lock lock = new ReentrantLock(); //How to sell tickets public void sale() { lock.lock(); // Open lock try { if (number > 0) { System.out.println(Thread.currentThread().getName() + "Sold the second" + (number--) + "Ticket remaining" + number + "Ticket"); } }finally { lock.unlock(); // Close the lock } } }
3Synchronized vs Lock
1. Synchronized built-in Java keyword, Lock is a Java class
2. Synchronized cannot judge the status of obtaining the lock. Lock can judge whether the lock has been obtained
3. Synchronized will automatically release the lock. Lock must be manually locked and manually released! Deadlock may be encountered
4. Synchronized thread 1 (get lock - > block), thread 2 (wait); Lock will not necessarily wait all the time. Lock will have a trylock to try to obtain the lock, which will not cause a long wait.
5. Synchronized is a reentrant lock, non interruptible and unfair; Lock, which is reentrant, can judge the lock, and can set its own fair lock and unfair lock;
6. Synchronized is suitable for locking a small number of code synchronization problems, and Lock is suitable for locking a large number of synchronization codes;
What is a lock and how to judge what a lock is?
4 producer and consumer issues
1synchronized version
public class ConsumeAndProduct { public static void main(String[] args) { Data data = new Data(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); } } class Data { private int num = 0; // +1 public synchronized void increment() throws InterruptedException { // Judgment waiting if (num != 0) { this.wait(); } num++; System.out.println(Thread.currentThread().getName() + "=>" + num); // Notify other threads that + 1 execution is complete this.notifyAll(); } // -1 public synchronized void decrement() throws InterruptedException { // Judgment waiting if (num == 0) { this.wait(); } num--; System.out.println(Thread.currentThread().getName() + "=>" + num); // Notify other threads that - 1 execution is complete this.notifyAll(); } }
A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0
There is a problem (false wake-up)
Problem: if there are four threads, a false wake-up will occur
Solution: change if to while to prevent false wake-up. wait will release the lock!!!
Conclusion: if it is judged by if, the thread will start running from the code after the wait after waking up, but will not re judge the if condition. It will directly continue to run the code after the if code block. If it is used while, it will also run from the code after the wait, but the loop condition will be re judged after waking up. If it is not true, execute the code block after the while code block, and continue to wait if it is true.
This is why we use while instead of if, because after the thread is awakened, the execution starts after wait
package com.marchsoft.juctest; /** * Description: * * @author jiaoqianjin * Date: 2020/8/10 22:33 **/ public class ConsumeAndProduct { public static void main(String[] args) { Data data = new Data(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } } class Data { private int num = 0; // +1 public synchronized void increment() throws InterruptedException { // Judgment waiting while (num != 0) { this.wait(); } num++; System.out.println(Thread.currentThread().getName() + "=>" + num); // Notify other threads that + 1 execution is complete this.notifyAll(); } // -1 public synchronized void decrement() throws InterruptedException { // Judgment waiting while (num == 0) { this.wait(); } num--; System.out.println(Thread.currentThread().getName() + "=>" + num); // Notify other threads that - 1 execution is complete this.notifyAll(); } }
2Lock version
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockCAP { public static void main(String[] args) { Data2 data = new Data2(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } } class Data2 { private int num = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // +1 public void increment() throws InterruptedException { lock.lock(); try { // Judgment waiting while (num != 0) { condition.await(); } num++; System.out.println(Thread.currentThread().getName() + "=>" + num); // Notify other threads that + 1 execution is complete condition.signalAll(); }finally { lock.unlock(); } } // -1 public void decrement() throws InterruptedException { lock.lock(); try { // Judgment waiting while (num == 0) { condition.await(); } num--; System.out.println(Thread.currentThread().getName() + "=>" + num); // Notify other threads that + 1 execution is complete condition.signalAll(); }finally { lock.unlock(); } } }
Advantages of Condition
Accurate notification and wake-up threads!
What if we want to specify the next order of notification? We can use Condition to specify the notification process
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Description: * A Call B after execution * B Call C after execution * C Call A after execution **/ public class ConditionDemo { public static void main(String[] args) { Data3 data3 = new Data3(); new Thread(() -> { for (int i = 0; i < 10; i++) { data3.printA(); } },"A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { data3.printB(); } },"B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { data3.printC(); } },"C").start(); } } class Data3 { private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int num = 1; // 1A 2B 3C public void printA() { lock.lock(); try { // Business code judgment - > execution - > notification while (num != 1) { condition1.await(); } System.out.println(Thread.currentThread().getName() + "==> AAAA" ); num = 2; condition2.signal(); }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } public void printB() { lock.lock(); try { // Business code judgment - > execution - > notification while (num != 2) { condition2.await(); } System.out.println(Thread.currentThread().getName() + "==> BBBB" ); num = 3; condition3.signal(); }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } public void printC() { lock.lock(); try { // Business code judgment - > execution - > notification while (num != 3) { condition3.await(); } System.out.println(Thread.currentThread().getName() + "==> CCCC" ); num = 1; condition1.signal(); }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } }
A==> AAAA B==> BBBB C==> CCCC A==> AAAA B==> BBBB C==> CCCC A==> AAAA B==> BBBB C==> CCCC . . . . . .
5.8 lock phenomenon
How to judge who the lock is! Who is the lock?
Lock will lock: object, Class
Deep understanding of our locks
Question 1
Two synchronization methods, SMS or phone first
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { phone.sendMs(); }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.call(); }).start(); } } class Phone { public synchronized void sendMs() { System.out.println("send message"); } public synchronized void call() { System.out.println("phone"); } }
The output result is
send message
phone
Question 2:
Let's look again: we delay texting by 4s
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { phone.sendMs(); }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.call(); }).start(); } } class Phone { public synchronized void sendMs() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("send message"); } public synchronized void call() { System.out.println("phone"); } }
What is the result now?
Text message first, then call again!
why?
Reason: it is not executed in sequence, but the object locked by synchronized is the call of method! For the two methods, the same lock is used. Who gets it first and who executes it first, and the other one waits
Question three
Add a common method
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { Phone phone = new Phone(); new Thread(() -> { try { phone.sendMs(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { phone.hello(); }).start(); } } class Phone { public synchronized void sendMs() throws InterruptedException { TimeUnit.SECONDS.sleep(4); System.out.println("send message"); } public synchronized void call() { System.out.println("phone"); } public void hello() { System.out.println("hello"); } }
The output result is
hello
send message
Reason: hello is a common method, which is not affected by the synchronized lock and does not need to wait for the release of the lock
Question 4
If we use two objects, one calling to send a text message and the other calling to make a phone call, what is the whole order?
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { try { phone1.sendMs(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { phone2.call(); }).start(); } } class Phone { public synchronized void sendMs() throws InterruptedException { TimeUnit.SECONDS.sleep(4); System.out.println("send message"); } public synchronized void call() { System.out.println("phone"); } public void hello() { System.out.println("hello"); } }
Output results
phone
send message
Reason: two objects have two locks, and there will be no waiting. I slept for 4s after texting, so I made a call first
Questions 5 and 6
If we add static to the synchronized method, it will become a static method! So what is the order?
(1) Let's call two methods with one object!
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { Phone phone = new Phone(); new Thread(() -> { try { phone.sendMs(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { phone.call(); }).start(); } } class Phone { public static synchronized void sendMs() throws InterruptedException { TimeUnit.SECONDS.sleep(4); System.out.println("send message"); } public static synchronized void call() { System.out.println("phone"); } }
The answer is: text first, then call
(2) If we use two objects to call two methods!
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { try { phone1.sendMs(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { phone2.call(); }).start(); } } class Phone { public static synchronized void sendMs() throws InterruptedException { TimeUnit.SECONDS.sleep(4); System.out.println("send message"); } public static synchronized void call() { System.out.println("phone"); } }
The answer is: send text messages first and then call
Why? Why is the previous object always executed first when static is added! Why wait behind?
The reason is: for static static methods, there is only one copy for the whole Class. For different objects, the same method is used, which means that this method belongs to this Class. If static static methods use synchronized locking, the synchronized lock will lock the whole object! No matter how many objects, there is only one static lock. Whoever gets the lock first will execute it first, and other processes need to wait!
Question 7
If we use a static synchronization method, a synchronization method and an object, what is the calling order?
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { Phone phone = new Phone(); new Thread(() -> { try { phone.sendMs(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { phone.call(); }).start(); } } class Phone { public static synchronized void sendMs() throws InterruptedException { TimeUnit.SECONDS.sleep(4); System.out.println("send message"); } public synchronized void call() { System.out.println("phone"); } }
Output results
phone
send message
Reason: because one lock is the template of Class and the other lock is the caller of object. So there is no waiting, run directly.
Question 8
If we use a static synchronization method, a synchronization method and two objects, what is the calling order?
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { try { phone1.sendMs(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { phone2.call(); }).start(); } } class Phone { public static synchronized void sendMs() throws InterruptedException { TimeUnit.SECONDS.sleep(4); System.out.println("send message"); } public void call() { System.out.println("phone"); } }
Output results
phone
send message
Reason: the two locks are not the same thing
Summary
this from new is a specific object
static Class is the only template
6. Unsafe assembly
1List is not safe
import java.util.ArrayList; import java.util.List; import java.util.UUID; //java. util. Concurrent modificationexception concurrent modification exception! public class Test { 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(); } } }
Will cause util. Concurrent modificationexception concurrent modification exception!
ArrayList is not safe in the case of concurrency
import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; public class Test { public static void main(String[] args) { /** * Solution * 1. List<String> list = new Vector<>(); * 2. List<String> list = Collections.synchronizedList(new ArrayList<>()); * 3. List<String> list = new CopyOnWriteArrayList<>(); */ List<String> list = new CopyOnWriteArrayList<>(); for (int i = 1; i <=10; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }
CopyOnWriteArrayList: copy when writing! An optimization strategy in the field of COW computer programming
The core idea is that if multiple Callers require the same resources (such as memory or data storage on disk) at the same time, they will jointly obtain the same pointer to the same resources. The system will not really copy a private copy to the caller until a caller's view modifies the resource content, while the original resources seen by other Callers remain unchanged. This process is transparent to other Callers. The main advantage of this method is that if the caller does not modify the resource, no private copy will be created. Therefore, multiple Callers can share the same resource only when reading.
There is no need to lock when reading. If multiple threads are adding data to CopyOnWriteArrayList when reading, the old data will still be read, because the old CopyOnWriteArrayList will not be locked when writing.
When multiple threads call, list, read, fixed, write (overwrite operation exists); Avoid overwriting when writing, resulting in data disorder;
CopyOnWriteArrayList is better than Vector. Where is it?
The bottom layer of Vector is realized by using the synchronized keyword: it is particularly inefficient.
CopyOnWriteArrayList uses Lock lock, which will be more efficient!
2set unsafe
Set and List are the same: in the case of multithreading, ordinary set is thread unsafe;
There are two solutions:
Set class wrapped with synchronized of Collections tool class
Write the replicated JUC solution using CopyOnWriteArraySet
public class SetTest { public static void main(String[] args) { /** * 1. Set<String> set = Collections.synchronizedSet(new HashSet<>()); * 2. Set<String> set = new CopyOnWriteArraySet<>(); */ // Set<String> set = new HashSet<>(); Set<String> set = new CopyOnWriteArraySet<>(); for (int i = 1; i <= 30; i++) { new Thread(() -> { set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); },String.valueOf(i)).start(); } } }
What is the bottom layer of HashSet?
The bottom layer of hashSet is a HashMap;
3Map is not safe
//Is map used like this? No, not at work //What is the default equivalence? new HashMap<>(16,0.75); Map<String, String> map = new HashMap<>(); //Load factor, initialization capacity
The default loading factor is 0.75 and the default initial capacity is 16 (there is a problem here)
The same HashMap basic class also has concurrent modification exceptions!
public class MapTest { public static void main(String[] args) { //Is map used like this? No, not at work //What is the default equivalence? new HashMap<>(16,0.75); /** * Solution * 1. Map<String, String> map = Collections.synchronizedMap(new HashMap<>()); * Map<String, String> map = new ConcurrentHashMap<>(); */ Map<String, String> map = new ConcurrentHashMap<>(); //Load factor, initialization capacity for (int i = 1; i < 100; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } }
7Callable
1. Can have return value;
2. Exceptions can be thrown;
3. Different methods, run()/call()
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { for (int i = 1; i < 10; i++) { MyThread1 myThread1 = new MyThread1(); FutureTask<Integer> futureTask = new FutureTask<>(myThread1); // Put it into Thread and the result will be cached new Thread(futureTask,String.valueOf(i)).start(); // This get method may be blocked. If it is a time-consuming method in the call method, we will usually put this last or use asynchronous communication int a = futureTask.get(); System.out.println("Return value:" + a); } } } class MyThread1 implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("call()"); return 1024; } }
8 common auxiliary classes
1CountDownLatch subtraction counter
import java.util.concurrent.CountDownLatch; public class Test { public static void main(String[] args) throws InterruptedException { // The total number is 6. When you have to perform a task, you can use it again! 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 for the counter to return to zero, wakes up, and then continues to run downward
2CyclicBarrier addition counter
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class Test { public static void main(String[] args) { // Main thread //public CyclicBarrier(int parties, Runnable barrierAction) CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> { System.out.println("Summon the Dragon"); }); for (int i = 1; i <= 7; i++) { // Child thread final 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(); } } }
3Semaphore current limiting
import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) { // Number of threads, parking space, current limiting Semaphore semaphore = new Semaphore(3); for (int i = 0; i <= 6; i++) { new Thread(() -> { // acquire() gets try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "Grab a parking space"); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName() + "Leave the parking space"); }catch (Exception e) { e.printStackTrace(); }finally { semaphore.release(); // Release } }).start(); } } }
Thread-0 Grab a parking space Thread-1 Grab a parking space Thread-2 Grab a parking space Thread-1 Leave the parking space Thread-0 Leave the parking space Thread-3 Grab a parking space Thread-4 Grab a parking space Thread-2 Leave the parking space Thread-5 Grab a parking space Thread-4 Leave the parking space Thread-5 Leave the parking space Thread-3 Leave the parking space Thread-6 Grab a parking space Thread-6 Leave the parking space
Principle:
semaphore.acquire() gets 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!