“This is the second day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021”

concurrent

multithreading

Three ways to implement multithreading

Runnable

  1. class CountDown implements Runnable

New Thread(new CountDown(),”Runnable”).start(); //new Thread() the second argument is the Thread name method definition:

public void run() {
}
Copy the code

Callable

  1. class ThreadCall implements Callable<String>String is replaceable and is the value of the callback

Bootstrap: Use the FutrueTask class as a simple adaptation to use this interface

FutureTask<String> ft = new FutureTask<>(new ThreadCall());
new Thread(ft,"Callable").start()
Copy the code

Method definition:

@Override
public String call() throws Exception {
    return "";
}
Copy the code

Note that (1) The Callable method is call(), while Runnable method is run(). (2) The Callable task can return a value, while Runnable task cannot return a value. To support this function, Java provides a Callable interface. (3) The call() method can throw an exception, but the run() method cannot. (4) Run the Callable task to get a Future object, which represents the result of the asynchronous calculation. (5) FutureTask’s get() method is used to obtain the execution result. This method blocks and will not return until the task is completed.

Thread

  1. class MyThread extends Thread

Startup mode:

new MyThread().start();
Copy the code

Method definition:

public void run() {
}
Copy the code
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; class SynTest { public static void main(String[] args) throws ExecutionException, InterruptedException { new Thread(new CountDown(),"Runnable").start(); new MyThread().start(); int time = 5; FutureTask<String> ft = new FutureTask<>(new ThreadCall()); new Thread(ft,"Callable").start(); String output=ft.get(); System.out.println("Callable output is:"+output); System.out.println(" Here are the blocked threads "); while(time>=0){ System.out.println(Thread.currentThread().getName() + ":" +time ); time--; try { Thread.sleep(1000); // Sleep for 1 second} catch (InterruptedException e) {e.printstackTrace (); } } } } class ThreadCall implements Callable<String> { @Override public String call() throws Exception { // TODO Auto-generated method stub int time=5; while (time>0) { System.out.println(Thread.currentThread().getName() + ":" + time--); try { Thread.sleep(1000); // Sleep for 1 second} catch (InterruptedException e) {e.printstackTrace (); } } return time+""; } } class MyThread extends Thread{ int time=5; public void run() { Thread.currentThread().setName("Thread"); while (time>=0){ System.out.println(Thread.currentThread().getName() + ":" + time--); try { Thread.sleep(1000); // Sleep for 1 second} catch (InterruptedException e) {e.printstackTrace (); } } } } class CountDown implements Runnable{ int time = 5; public void run() { while(time>=0){ System.out.println(Thread.currentThread().getName() + ":" + time--); try { Thread.sleep(1000); // Sleep for 1 second} catch (InterruptedException e) {e.printstackTrace (); }}}}Copy the code

Thread synchronization

The competition state

When multiple threads access the same common resource and cause conflicts that cause threads to be out of sync, we call this state a race state.

Thread safety

A StringBuffer is thread-safe, and a StringBuilder is thread-safe. For example, a StringBuffer is thread-safe and a StringBuilder is thread-safe.

A critical region

The reason for the state of competition is that multiple threads simultaneously enter a specific part of the program, which is called the critical area in the program. We need to solve the problem of multi-thread asynchronism, which is to solve how to make multiple threads orderly access to the critical area.

Java often uses methods when dealing with thread synchronization

1. Synchronized keyword.

2. Lock Indicates locking.

3. Semaphore.

The volatile keyword

The volatile keyword is often used in concurrent programming to ensure visibility and order

Java memory model

All variables are stored in main memory. Each thread also has its own working memory, which holds variables used by the thread (these variables are copied from main memory). All operations (reads, assignments) by a thread to a variable must be done in working memory. Different threads cannot directly access variables in each other’s working memory, and the transfer of variable values between threads needs to be completed through the main memory.

Based on this memory model, the problem of data “dirty read” in multithreaded programming arises. For example, two threads read 10 and store it in their respective working memory. Then thread 1 adds 100 and writes the latest value of I, 101, to memory. Now thread 2’s working memory has the value of I still 10, and after adding 1, it has the value of I 11, and then thread 2 writes the value of I to memory, which is 11, but it wants 111.

The three concepts of concurrent programming are atomicity, orderliness, and visibility

For concurrent programs to execute correctly, atomicity, visibility, and order must be guaranteed. As long as one of them is not guaranteed, it may cause the program to run incorrectly.

atomic

Atomicity: An operation or operations are either all performed without interruption by any factor, or none at all. The Java memory model only guarantees that basic reads and assignments are atomic operations, and that atomicity for broader operations can be achieved through synchronized and Lock. Since synchronized and Lock guarantee that only one thread executes the code block at any one time, atomicity is naturally eliminated and thus guaranteed

visibility

Visibility means that when multiple threads access the same variable and one thread changes the value of the variable, other threads can immediately see the changed value.

// thread 1 executes code int I = 0; i = 10; // thread 2 executes code j = I;Copy the code

When thread 1 executes the statement I =10, it loads the initial value of I into the working memory and then assigns the value to 10, so the value of I in the working memory of thread 1 becomes 10, but is not immediately written to main memory. If thread 2 executes j = I, it will fetch the value of I from main memory and load it into thread 2’s working memory. Note that the value of I in memory is still 0, so that the value of j is 0 instead of 10. This is the visibility problem. After thread 1 makes changes to variable I, thread 2 does not immediately see the value changed by thread 1. Java provides the volatile keyword for visibility. When a shared variable is volatile, it guarantees that the changed value is immediately updated to main memory, and that it will read the new value in memory when another thread needs to read it.

Common shared variables do not guarantee visibility, because it is uncertain when a common shared variable will be written to main memory after modification. When another thread reads a common shared variable, it may still have the old value in memory, so visibility cannot be guaranteed. Visibility is also guaranteed by synchronized and Lock, which ensure that only one thread at a time acquies the Lock and executes the synchronization code, flushing changes to variables into main memory before releasing the Lock. So visibility is guaranteed.

Orderliness: In other words, the sequence of program execution is reordered according to the sequence of code execution. Generally speaking, the processor may optimize the input code to improve the efficiency of program operation, which does not ensure that the sequence of execution of each statement in the program is the same as that in the code. However, it ensures that the final execution of the program is consistent with the sequential execution of the code because the processor considers the data dependence between instructions when reordering. If Instruction Instruction 2 must use Instruction Instruction 1, The processor guarantees that Instruction 1 will be executed before Instruction 2

// thread 1: context = loadContext(); // statement 1, load configuration environment inited = true; // thread 2: while(! Inited){// Wait to load config environment sleep()} doSomethingwithconfig(context); // Work with the configuration environmentCopy the code

There are no data dependencies on statements 1 and 2 and may be reordered. If a reorder occurs, thread 1 executes statement 2 first, and thread 2 thinks the initialization is done, it will break out of the while loop and execute the doSomethingwithconfig(Context) method when the context has not been initialized, It will cause the program to fail.

Instruction reordering does not affect the execution of a single thread, but does affect the correctness of concurrent execution of the thread.

Volatile guarantees visibility

Once a shared variable (a member variable of a class, or a static member variable of a class) is volatile, two levels of semantics are achieved: 1) It is visible to different threads operating on the variable, i.e. when one thread changes the value of a variable, the new value is immediately visible to other threads.

// If stop is not volatile, it is highly unlikely that thread 2 will continue to loop because thread 1 is unaware of thread 2's changes to the stop variable after thread 2 has changed the stop variable, but before writing to main memory, thread 2 has moved on to something else. // thread 1 Boolean stop = false; while(! stop){ doSomething(); } // thread 2 stop = true;Copy the code

First, using the volatile keyword forces the modified value to be written to main memory immediately.

Second, using volatile will invalidate the stop line in thread 1’s working memory when thread 2 changes it (or the L1 or L2 line on the CPU).

Third: thread 1 reads stop again from main memory because the cache line of stop is invalid in thread 1’s working memory.

Volatile does not guarantee atomicity

Note: Increment operation does not have atomicity, it includes reading the original value of the variable, increment operation and writing to the working memory.

Thread 1 increments the variable. Thread 1 reads the original value of variable inc, and thread 1 blocks

Thread 2 increments the variable, and thread 2 also reads the original value of variable inc. Thread 1 only reads the variable inc, but does not modify the variable, so it will not invalidate the cache line of variable Inc in thread 2’s working memory, nor refresh the value in main memory. Therefore, thread 2 will directly go to main memory to read the value of inc. When it finds the value of inc, it increments by 1, writes 11 to working memory, and then writes it to main memory

Since thread 1 has read the value of inc, note that the value of inc in thread 1’s working memory is still 10. (If we assign inc to a, which in turn assigns it to B, the value of inc is refreshed (inc==10 is false!!). So thread 1 increments inc to 11, then writes 11 to working memory, and finally to main memory

So after the two threads each perform an increment operation, Inc only increases by 1

Volatile ensures order

The volatile keyword prevents instruction reordering in two ways: volatile prevents instruction reordering, so volatile guarantees some degree of order.

1) When a program performs a read or write to a volatile variable, all changes to the preceding operation must have occurred and the result must be visible to subsequent operations; The operation behind it has certainly not taken place;

2) During instruction optimization, statements following or following volatile variables must not be executed, nor must statements following volatile variables be executed in front of them.

The synchronized keyword

I don’t know the usage of synchronized and notify, as well as the lock method, the implementation principle of the lock class, and semaphore, but THE specific usage is almost forgotten. Synchronized methods can support a simple strategy to prevent thread interference and memory consistency errors: if an object is visible to multiple threads, all reads or writes to that object’s variables are done through Synchronized methods.

Simple means Synchronized is the most common and simple method to solve concurrency problems in Java. It can ensure that only one thread can execute synchronous code at most at the same time, so as to ensure the effect of concurrency safety in multi-threaded environment. If a piece of code is modified by Synchronized, the code is executed atomically, and when multiple threads execute the code, they are mutually exclusive, do not interfere with each other, and do not execute simultaneously.

The working mechanism of Synchronized is to use a lock in a multi-threaded environment. When the first thread executes it, it can only execute the lock by acquiring it. Once it is acquired, it will monopolise the lock until the execution is completed or release the lock under certain conditions.

Synchronized is a Java keyword, supported by Java native, and is the most basic synchronization lock. It modifies the following objects: 1. Modifies a code block. The modified code block is called a synchronous statement block, and its scope is the code enclosed in braces {}. 2. Modify a method. The modified method is called synchronous method, and its scope is the whole method, and the object is the object that calls the method. 3. Modify a static method that applies to the entire static method and all objects of the class. 4. Modify a class whose scope is the part enclosed in parentheses after synchronized, and whose main objects are all objects of the class.

An example of thread insecurity

Ticket agent question:

Public static void main(String[] args) {// Write your code here MyThread1 r1=new MyThread1(); Thread t1=new Thread(r1,"First_Thread1"); Thread t2=new Thread(r1,"Second_Thread2"); Thread t3=new Thread(r1,"Third_Thread2"); Thread t4=new Thread(r1,"Forth_Thread2"); t1.start(); t2.start(); t3.start(); t4.start(); } static class MyThread1 implements Runnable{ public static Integer num=10; @override public void run() {while (num>0){try {thread.sleep (1000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (thread.currentThread ().getname ()+ num--+ num); Forth_Thread2 is selling 10 tickets. Forth_thread1 is selling 9 tickets. Third_Thread2 is selling 8 tickets Forth_Thread2 is selling five tickets. Second_Thread2 is selling six tickets. Third_Thread2 is selling three tickets Forth_Thread2 is selling 1 ticket First_Thread1 is selling 2 tickets Third_Thread2 is selling 0 tickets Second_Thread2 is selling -1 ticketCopy the code

Synchronized modification method

Synchronized modifiers do not need to take a string argument if synchronized locks the entire method, and default to this

In this case, there are two threads competing to sell tickets, so it’s not appropriate to lock the entire method

static class MyThread1 implements Runnable{ public static Integer num=10; @Override public synchronized void run() { while (num>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (thread.currentThread ().getname ()+ num--+ num); First_Thread1 is selling 10 tickets. First_Thread1 is selling 9 tickets. First_Thread1 is selling 8 tickets First_Thread1 is selling 6 tickets First_Thread1 is selling 5 tickets First_Thread1 is selling 4 tickets First_Thread1 is selling 3 tickets First_Thread1 is selling 2 tickets First_Thread1 is selling a ticketCopy the code

Synchronized () locks objects

Synchronized modifies code blocks

It is possible to define a public STR static variable. If static is not defined, the STR for both threads is thread owned, not public

Synchronized (” “){// Synchronized (” “){} can be used to lock a block of code that needs to be synchronized (” “){}

static class MyThread1 implements Runnable{ public static Integer num=10; public static String str = new String("weimeig"); @Override public void run() { while (true){ synchronized (str){ if(num>0){ System.out.println( CurrentThread ().getname (); }else { break; } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }}}} Forth_Thread2 is selling 10 tickets Second_Thread2 is selling 9 tickets First_Thread1 is selling 8 tickets Third_Thread2 is selling 7 tickets Third_Thread2 Forth_Thread2 is selling five tickets. Forth_thread1 is selling four tickets. Second_Thread2 is selling three tickets A ticket is on saleCopy the code

wait notify

  1. Wait () blocks the current thread if the lock is obtained first. It is used with the synchronized keyword, that is, wait() and notify/notifyAll() methods are used in synchronized code blocks.
  2. Since wait() and notify/notifyAll() execute in a synchronized block, the current thread must have acquired the lock.

When a thread executes wait(), it releases the current lock and then releases the CPU to wait. Only when notify/notifyAll() is executed does one or more of the waiting threads wake up and continue until synchronized blocks are executed or wait() is encountered and the lock is released again. That is, notify/notifyAll() only wakes up the sleeping thread, but does not immediately release the lock, which depends on the execution of the block. Therefore, in programming, it is best to exit the critical region immediately after using notify/notifyAll() to wake up other threads so that they can acquire the lock. 3. Wait () should be surrounded by a try catch, so that the thread waiting for wait can also wake up when an abnormal interrupt occurs. Notify only wakes up a waiting thread and causes it to start executing. So if there are multiple threads waiting for an object, this method will only wake up one of them, depending on the operating system’s implementation of multithreaded management. NotifyAll wakes up all waiting threads, although which thread will be the first to process depends on the operating system implementation. The notifyAll method is recommended if multiple threads need to be awakened in the current situation. In producer-consumer scenarios, for example, all consumers or producers need to be woken up each time to determine whether the program can proceed.

import java.util.LinkedList; import java.util.Queue; public class ProducerAndConsumer { private final int MAX_LEN = 10; private Queue<Integer> queue = new LinkedList<Integer>(); class Producer extends Thread { @Override public void run() { producer(); } private void producer() { while(true) { synchronized (queue) { while (queue.size() == MAX_LEN) { queue.notify(); System.out.println(" Current queue full "); try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.add(1); queue.notify(); System.out.println(" producer produces a task, current queue length is "+ queue.size()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } } class Consumer extends Thread { @Override public void run() { consumer(); } private void consumer() { while (true) { synchronized (queue) { while (queue.size() == 0) { queue.notify(); System.out.println(" Current queue is empty "); try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.poll(); queue.notify(); System.out.println(" consumer consumes a task, current queue length is "+ queue.size()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) { ProducerAndConsumer pc = new ProducerAndConsumer(); Producer producer = pc.new Producer(); Consumer consumer = pc.new Consumer(); producer.start(); consumer.start(); }}Copy the code

Condition await and signal() under Lock

The same approach is used to implement wait/notification under Java Lock. On the whole, Object wait and notify/notify cooperate with Object monitor to complete the wait/notification mechanism between threads, while Condition and Lock cooperate to complete the wait notification mechanism. The former is Java low-level level, and the latter is language level, which has higher controllability and expansibility

Void signal() : Wakes a thread waiting on condition and moves it from the wait queue to the synchronization queue. If the synchronization queue can compete for Lock, the thread can return from the wait queue. Void signalAll() : Differs from 1 in that it wakes up all threads waiting on condition

Void await() throws InterruptedException: The current thread enters the wait state, If another thread calls the signal or signalAll methods of condition and the current thread gets a Lock and returns from the await method, it will raise an interrupted exception if it is interrupted in the wait state. Long awaitNanos(Long nanosTimeout) : The current thread enters the wait state until notified, interrupted, or timed out; Boolean await(long time, TimeUnit unit)throws InterruptedException: Boolean awaitUntil(Date deadline) throws InterruptedException: The current thread enters the wait state until it is notified, interrupted, or reaches a certain time

Condition advantages

  1. Condition can not respond to interrupts, but not by using Object.
  2. Condition can support multiple wait queues (new multiple Condition objects), while Object can only support one.
  3. Condition supports timeout while Object does not

.

The Lock interface

Concept of the lock

  1. Lock is an interface, similar to List
  2. The Lock and ReadWriteLock interfaces are the root interfaces of the two locks
  3. Lock means the implementation class is ReentrantLock.
  4. The representative implementation class for ReadWriteLock is ReentrantReadWriteLock.
  5. Condition interfacesNotesconditional variables that might be associated with a lock. These variables are similar in usage to implicit monitors accessed using Object.wait, but provide more power. In particular, a single Lock can be associated with multiple Condition objects. To avoid compatibility issues, the Condition method has a different name than the corresponding Object version.

Lock the noun

1. Reentrant lock

Reentrant means that a thread that has acquired a lock can acquire it again without a deadlock. The premise is the same object or class lock allocation mechanism: thread-based allocation, not method-call-based allocation. Reentrant means that the same lock can be acquired repeatedly. Synchronized and ReentrantLock are reentrant records the ID of the thread that owns the lock and the number of times the lock was acquired. When the lock is acquired again, it waits if the lock is not its own, and if it is, the total is +1. When released, the number of locks held is reduced by one. Lock (); lock(); lock(); When ReentrantLock, manually release the lock and lock the same number of times as release

lock.lock();
lock.lock();
lock.lock();
lock.unlock();
lock.unlock();
lock.unlock();
Copy the code

Purpose: Reentrant locks are used to avoid deadlocks.

2. Interruptible lock

Interruptible locks are locks that respond to interrupts. In Java, synchronized is not a breakable Lock and Lock is a breakable Lock. If thread A is executing the code in the lock, and thread B is waiting to acquire the lock, maybe because the waiting time is too long, thread B doesn’t want to wait and wants to do other things first, we can tell it to interrupt itself or interrupt it in another thread, this is called interruptible lock. The interruptibility of Lock was demonstrated earlier when the use of tryLock(Long time, TimeUnit Unit) and lockInterruptibly() was demonstrated.

3. Fair lock

Fair locking means that locks are acquired in the order in which they are requested. For example, if there are multiple threads waiting for a lock, when the lock is released, the thread that waited the longest (the thread that requested it first) gets the lock. This is a fair lock. Non-fair locks do not guarantee that locks are acquired in the order in which they were requested, which may result in one or more threads never acquiring the lock.

In Java, synchronized is an unfair lock that does not guarantee the order in which waiting threads acquire the lock. For ReentrantLock and ReentrantReadWriteLock, it is an unfair lock by default, but can be set to a fair lock

4. The spin lock

Spinlock: When a thread is acquiring a lock, if the lock has already been acquired by another thread, the thread will wait in a loop, and then continuously determine whether the lock can be acquired successfully, until the lock is acquired and will exit the loop. Advantages: The thread state will not be switched because of the spin lock. The thread is always in the user state, that is, the thread is always active. The thread will not enter the blocking state, reducing the unnecessary context switch. The execution speed is fast. The non-spin lock will enter the blocking state when it cannot obtain the lock, thus entering the kernel state, and the thread context switch is required to recover from the kernel state when it obtains the lock. (After the thread is blocked, it will enter the kernel (Linux) scheduling state, which will cause the system to switch back and forth between user state and kernel state, seriously affecting the lock performance.) disadvantages:

  1. If one thread holds the lock for too long, it can cause other threads waiting to acquire the lock to go into a loop, consuming CPU. Improper CPU usage may result in high CPU usage.
  2. The Java implementation above is not fair in that the thread that waits the longest gets the lock first. Unfair locks lead to “thread hunger.”

Based on the spin lock, it is possible to achieve fair and reentrant locking.

TicketLock: The fairness of the spin lock is realized in a manner similar to that of a bank number. However, due to the continuous read and write of serviceNum, each read and write operation must be cache synchronization between multiple processor caches, resulting in heavy system bus and memory traffic, which greatly reduces the overall system performance.

CLH is also a high-performance, fair spinlock based on a one-way linked list (created implicitly). The thread applying the lock only needs to spin on the local variable of its precursor node, which greatly reduces the number of unnecessary processor cache synchronizations and reduces the bus and memory overhead.

public interface Lock { void lock(); void unlock(); } public class QNode { volatile boolean locked; } import java.util.concurrent.atomic.AtomicReference; Public class CLHLock implements Lock {// tail, which is common to all threads. Private Final AtomicReference<QNode> tail; // The reference to the variable, not its contents, is immutable. // The precursor node is unique to each thread. private final ThreadLocal<QNode> myPred; // The current node, representing itself, is unique to each thread. private final ThreadLocal<QNode> myNode; {public CLHLock (). This.tail = new AtomicReference<QNode>(new QNode()); this.tail = new AtomicReference<QNode>(new QNode()); this.myNode = new ThreadLocal<QNode>() { protected QNode initialValue() { return new QNode(); }}; this.myPred = new ThreadLocal<QNode>(); } @override public void lock() {QNode node = mynode.get (); // Set your state to true to obtain the lock. node.locked = true; // Put yourself at the tail of the queue and return the previous value. QNode pred = tail.getAndSet(node); QNode pred=tail.get(); tail.set(node); // Put the old node into the precursor node. myPred.set(pred); // Determine the state of the precursor node, and then walk away. while (pred.locked) { } } @Override public void unlock() { // unlock. Get your own node. Set your own locked to false. QNode node = myNode.get(); node.locked = false; myNode.set(myPred.get()); // Maybe for sudden interruption, queue jumping etc.}}Copy the code

MCSLock loops over nodes of local variables. The IMPLEMENTATION of MCS is based on a linked list, where each thread requesting a lock is a node on the list, and the thread keeps polling its own local variables to see if it has acquired the lock. The thread that has acquired the lock is responsible for notifying other threads when the lock is released, so the synchronization of the cache between cpus is much reduced and only occurs when the thread notifies another thread, reducing the system bus and memory overhead. The implementation is as follows:

AtomicReferenceFieldUpdater is a tool based on reflection, it can be specified to a given class of volatile reference fields for atomic updates. (Note that this field cannot be private)

This is a classic move

While (currentThread. Next == null) {// Step 5}Copy the code
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class MCSLock { public static class MCSNode { volatile MCSNode next; volatile boolean isWaiting = true; } volatile MCSNode queue; / / points to the last one to apply for the lock MCSNode private static final AtomicReferenceFieldUpdater < MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater .newUpdater(MCSLock.class, MCSNode.class, "queue"); public void lock(MCSNode currentThread) { MCSNode predecessor = UPDATER.getAndSet(this, currentThread); // Get the end of the queue value saved on the predecessor and add yourself to the end of the queue if (predecessor! Next = currentThread; While (currentThread.iswaiting) {} else {if (currentThread.iswaiting) {if (currentThread.iswaiting) {if (currentThread.iswaiting) { CurrentThread. IsWaiting = false; }} public void unlock(MCSNode currentThread) {if (currentThread. IsWaiting) { } if (currentThread. Next == null) {if (UPDATER.compareAndSet(this, currentThread, Null)) {// step 4 // compareAndSet returns true to indicate that no one is behind you; } else {// suddenly someone in line behind me, may not know who, following is waiting for the follow-up !!!!! // The reason for this is that: Step 1 after the execution, step 2 May not have performed while (currentThread. Next = = null) {/ / step 5}}} currentThread. Next, isWaiting = false; currentThread.next = null; // for GC } }Copy the code

CLHLock and MCSLock greatly improve performance by avoiding reduced processor cache synchronization through linked lists. The difference is that CLHLock polls the state of its precursor node, while MCS looks at the lock state of the current node.

CLHLock is problematic when used with NUMA architectures. In a NUMA architecture without cache, CLHLock is not an optimal spin-lock on a NUMA architecture because CLHLock spins on the node before the current node, and processors in A NUMA architecture access local memory faster than they access memory on other nodes over the network.

     

The Lock interface has six methods

Lock(), tryLock(), tryLock(long time, TimeUnit unit) and lockInterruptibly() are used to acquire locks. UnLock () is used to release locks. NewCondition () returns a newCondition instance bound to the Lock for collaboration between threads

Void lock() // If the current thread is not interrupted, the lock is obtained, Void lockInterruptibly() // returns a newCondition instance bound to this Lock instance Condition newCondition() // the Lock is acquired only if the Lock is idle at the time of invocation, Boolean tryLock() // If the lock is free within the given wait time and the current thread is not interrupted, Boolean tryLock(long time, TimeUnit unit) // Unlock void unlock()Copy the code

Use the lock ()

Lock lock = ... ; //=new ReentrantLock(); lock.lock(); Try {// process tasks}catch(Exception ex){}finally{lock.unlock(); // Release the lock! }Copy the code

tryLock()

Lock lock = ... ; If (lock.trylock ()) {try{// process the task}catch(Exception ex){}finally{lock.unlock(); // If you can't get the lock, do something else.Copy the code

lockInterruptibly(); If the thread is waiting to acquire the lock, the thread can respond to the interrupt, which is the wait state of the interrupted thread. For example, when two threads attempt to acquire A lock using lock.lockinterruptibly () at the same time, calling threadb.interrupt () on threadB interrupts the wait for threadB if thread A has acquired the lock and threadB is waiting. Since the lockInterruptibly() declaration throws an exception, lock.lockInterruptibly() must be placed in the try block or declare InterruptedException outside the method calling lockInterruptibly(), However, the latter synchronized is recommended. When a thread is waiting for a lock, it cannot be interrupted

public void method() throws InterruptedException { lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); }}Copy the code

ReentrantLock Lock Lock =new ReentrantLock(false); Parameters can be set to determine whether the lock is fair

ReadWriteLock lock=new ReentrantReadWriteLock(); lock.readLock().lock();

The difference between lock and synchronized

  1. There is a big difference between Lock and synchronized. Using synchronized does not require the user to manually release the Lock. When the synchronized method or synchronized code block is executed, the system will automatically let the thread release the Lock. A Lock must be manually released by the user. If the Lock is not actively released, a deadlock may occur.
  2. When resource competition is not very fierce, the performance of Synchronized is better than that of ReetrantLock, but when resource competition is very fierce, the performance of Synchronized will decrease by dozens of times, but the performance of ReetrantLock can maintain normal.
  3. The thread holding the lock is blocked waiting for IO or some other reason (such as calling sleep), but the lock is not released, so the other thread has to wait. The Lock can be resolved by either waiting only a certain amount of time: tryLock(long time, TimeUnit Unit)) or being able to respond to interrupts (solution: lockInterruptibly()).
  4. Synchronized When only one thread can read and multiple threads can read, lock solves the problem by reading and writing the lock
  5. Lock can tell if a thread has successfully acquired a Lock (solution: ReentrantLock), but synchronized cannot.

lock demo

static class MyThread1 implements Runnable{ public static Integer num=10; Lock lock=new ReentrantLock(); @Override public void run() { while (true){ try { lock.lock(); If (num>0){system.out.println (thread.currentThread ().getname ()); }else { break; } }finally { lock.unlock(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code

Semaphore

Semaphore is currently being extended to use in multi-threaded environments. Semaphore is an important concept in operating systems and has applications in process control. Semaphore control can be done easily with Java concurrent libraries. Semaphore controls how many resources can be accessed simultaneously, acquire a license through acquire(), wait if not, and release() releases a license. For example, in Windows, you can set the maximum number of clients that can access shared files.

Class TestSemaphore {public static void main(String[] args) {// Thread pool exec = Executors.newCachedThreadPool(); // Only 5 threads can access final Semaphore semp = new Semaphore(3); For (int index = 0; index < 10; index++) { final int NO = index; Runnable run = new Runnable() {public void run() {try {// synchronized (this){semp.acquire(); System.out.println(Thread.currentThread().getName()+ "Accessing: " + NO); Thread.sleep((long) (Math.random() * 1000)); / / after the visit, release System. Out. The println (" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "+ semp. AvailablePermits ()); semp.release(); }} catch (InterruptedException e) {e.printstacktrace (); }}}; exec.execute(run); } // exit the thread pool exec.shutdown(); }}Copy the code

The result is basically something like this

pool-1-thread-1Accessing: 0
pool-1-thread-2Accessing: 1
pool-1-thread-3Accessing: 2
-----------------0
pool-1-thread-4Accessing: 3
-----------------0
pool-1-thread-5Accessing: 4
-----------------0
pool-1-thread-6Accessing: 5
-----------------0
pool-1-thread-7Accessing: 6
-----------------0
pool-1-thread-8Accessing: 7
-----------------0
pool-1-thread-9Accessing: 8
-----------------0
pool-1-thread-10Accessing: 9
-----------------0
-----------------1
-----------------2

Process finished with exit code 0
Copy the code

The thread pool

The life cycle of a thread

Thread pool conceptA thread pool is a collection of threads that are created first, called a thread pool. Performance can be improved by using thread pools, which create a large number of idle threads at startupTasks are passed to the thread pool, the thread pool will start a thread to execute the task, after the execution, the thread will not die, but return to the thread pool to become idle, waiting to execute the next task.Advantages of thread poolsIn an application, we need to use threads multiple times, which means we need to create and destroy threads multiple times. The process of creating and destroying threads inevitably consumes memory. In Java, memory resources are extremely valuable, so we came up with the concept of thread pools. Multi-thread running time, the system constantly start and close the new thread, the cost is very high, will transition consumption of system resources, as well as transition switch thread danger, which may lead to the collapse of system resources. In this case, thread pools are the best choice

How to create a thread pool?

Java already provides a class for creating thread pools: Executor, and when we do, we typically use a subclass of ThreadPoolExecutor.

package com.mianshi.test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; Public class NewFixedThreadPoolTest {public static void main(String[] args) {// Create a fixed number of reusable thread pools fixedThreadPool = Executors.newFixedThreadPool(3); // create an idle thread for (int I = 0; i < 10; I++) {fixedthreadpool.execute (new Runnable() {// the program passes a task to the thread pool, and the thread pool starts a thread to execute the task. After execution, the thread does not die, but returns to the pool to become idle. Wait to execute the next task. System.out.println(thread.currentThread ().getName() + "Being executed "); public void run() {try {// Prints information about the executing cache Thread. Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }}}); }}}Copy the code
Pool-1 thread-1 is being executed Pool-1 thread-2 is being executed Pool-1 thread-3 is being executed Pool-1 thread-1 is being executed Pool-1 thread-2 is being executed Pool-1-thread-3 is being executed Pool-1-thread-1 is being executed Pool-1-thread-2 is being executed Pool-1-thread-3 is being executed Pool-1-thread-1 is being executed pool-1-thread-3 is being executed Each task sleeps 2 seconds after printing results, so 3 results are printed every 2 seconds. The size of the fixed-length thread pool is best set based on system resources. Such as the Runtime. GetRuntime (). AvailableProcessors ()Copy the code

Tasks for the thread pool

  1. Runnable objects can be thrown directly to threads to create Thread instances that are bound to Runnable. When the Thread instance calls the start() method, the Runnable task is actually executed in the Thread. Note: If the run() method is called directly without the start() method, it is just a normal method call. No new thread is started and the program is executed serially in the main thread.

  2. The Runnable object can also be thrown directly to the execute and Submit methods of the thread pool object to be executed by the thread pool for its bound threads.

  3. Runnable objects can also be further encapsulated as FutureTask objects and then thrown to the execute and Submit methods of the thread pool.

  4. Callable: Has less functionality than Runnable, cannot be used to create threads, and cannot be thrown directly to the execute method of the thread pool. But the call method has a return value.

  5. FutureTask: Is a further encapsulation of Runnable and Callable, and this task has a return value, which is stored in a data member of the FutureTask class called Outcome. Why is it possible to encapsulate a Runnable with no return value as a FutureTask? We’ll talk about that in a minute. FutureTask does more than just throw Runnable and Callable into the thread pool by monitoring the status of tasks in the pool. FutureTask is created slightly differently with Runnable and Callable.

class NewFixedThreadPoolTest { public static void main(String[] args) throws ExecutionException, InterruptedException {/ / create a reusable fixed number of thread pool ExecutorService fixedThreadPool = Executors. NewFixedThreadPool (3); ArrayList<FutureTask<String>> list=new ArrayList<>(); for (int i = 0; i < 10; i++) { FutureTask<String> ft=new FutureTask<String>(new Callable<String>() { @Override public String call() throws Exception { System.out.println(Thread.currentThread().getName()); Return c. }}); list.add(ft); fixedThreadPool.execute(ft); } fixedThreadPool.shutdown(); For (FutureTask<String> ft:list) {system.out.println (ft.get()); } the if (list. Get (0). The get () equals (" result "back off)) {System. Out. Println (" began to attack"); }}}Copy the code

The results

pool-1-thread-2 pool-1-thread-1 pool-1-thread-3 pool-1-thread-2 pool-1-thread-1 pool-1-thread-1 pool-1-thread-1 Pool-1-thread-3 Rollback Result Rollback Result Pool-1-thread-2 rollback result Rollback Result Pool-1-thread-1 rollback result Attack startsCopy the code