A brief talk about Juc concurrent programming — next
ReadWriteLock read-write lock
ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writes
Read and write locks are mutually exclusive and only one can be running at a time
It can be read by multiple threads simultaneously
Only one thread can write when writing
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/ * * *@Author if
* @Description: Read/write lock: mainly prevents multiple threads from writing and reading at the same time resulting in illusory * read lock: shared lock * write lock: exclusive lock *@Date 2021-11-06 上午 12:31
*/
public class ReadWriteLockDemo {
public static void main(String[] args) throws InterruptedException {
MyCache myCache=new MyCache();
//10 threads do write only
for(int i=1; i<=10; i++){final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp);
},"write"+i).start();
}
//10 threads only do reads
for(int i=1; i<=10; i++){int temp=i;
new Thread(()->{
myCache.get(temp + "");
},"read"+i).start(); }}// Custom cache
static class MyCache{
private volatile Map<String,Object> map=new HashMap<>();
// read/writeLock, you can use writeLock and readLock to obtain write and read locks, and then lock
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
// When writing, only one thread is expected to operate
public void put(String key,Object value){
// get writeLock writeLock from readWriteLock
Lock writeLock = readWriteLock.writeLock();
// Write lock to lock
writeLock.lock();
try{
System.out.println(Thread.currentThread().getName()+"Write key ="+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"Write complete");
}catch(Exception e){
e.printStackTrace();
}finally{
// Write the lock to unlock itwriteLock.unlock(); }}// Each thread is expected to read
public void get(String key){
Lock readLock = readWriteLock.readLock();
readLock.lock();
try{
System.out.println(Thread.currentThread().getName()+"Read key ="+key+",value = "+map.get(key));
}catch(Exception e){
e.printStackTrace();
}finally{ readLock.unlock(); }}}}Copy the code
Block queue BlockingQueue
When are blocking queues used: multi-threaded concurrent processing, thread pools
It’s a little bit like the producer-consumer problem
- Write: If the queue is full, it must block and wait
- Fetch: If the queue is empty, it must block to wait for production
BlockingQueue’s four sets of apis
way | An exception is thrown | No exception is thrown and a value is returned | Block waiting for | Timeout waiting for |
---|---|---|---|---|
add | add() | offer() | put() | offer(,,) |
remove | remove() | poll() | take() | poll(,) |
Determine the head of the queue | element() | peek | – | – |
Add operations are nulled, so null is not allowed
checkNotNull(e);
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
Copy the code
1. Throw an exception
Once the queue size is set, each of these operations throws an exception
- Queue full before adding:
IllegalStateException: Queue full
- Queue empty then fetch:
NoSuchElementException
// throw an exception
public static void throwException(a){
// The capacity parameter indicates the queue size
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// the first queue is a
System.out.println("blockingQueue.element() = " + blockingQueue.element());
// Set the queue size to 3 and throw an exception when a fourth element is added
//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());
/ / to empty the queue, then remove Java exception. Util. NoSuchElementException
// System.out.println(blockingQueue.remove());
// NoSuchElementException is thrown after the queue is emptied
System.out.println("blockingQueue.element() = " + blockingQueue.element());
}
Copy the code
The situation here is exactly the same as the exception for a normal LinkedList queue
Take a look at the source code for ArrayBlockingQueue
You can see that ArrayBlockingQueue calls its parent AbstractQueue’s add() method
The Add method calls the Offer method and actively throws an exception if the add fails (the Offer method of implement BlockingQueue).
public boolean add(E e) {
return super.add(e);
}
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
Copy the code
The Element method also calls the peek method
Is the empty-handed throwing anomaly
public E element(a) {
E x = peek();
if(x ! =null)
return x;
else
throw new NoSuchElementException();
}
public E peek(a) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally{ lock.unlock(); }}Copy the code
2. No exception is thrown and a value is returned
//2. No exception is thrown and a value is returned
public static void noExceptionAndReturn(a){
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// the first queue is a
System.out.println("blockingQueue.peek() = " + blockingQueue.peek());
// Full queue returns false
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// Queue empty, return null
System.out.println(blockingQueue.poll());
// Queue empty, no queue head, return null
System.out.println("blockingQueue.peek() = " + blockingQueue.peek());
}
Copy the code
3. Block and wait
//3
public static void blockWait(a) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println("Began to put");
new Thread(()->{
try {
blockingQueue.put("a");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
blockingQueue.put("b");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
blockingQueue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// Give 1 second for the thread to fill the queue
TimeUnit.SECONDS.sleep(1);
// When the queue is full, the blocking thread will be blocked from output
new Thread(()->{
try {
blockingQueue.put("d");
System.out.println(Thread.currentThread().getName()+"Put out");
} catch(InterruptedException e) { e.printStackTrace(); }},"Blocked thread").start();
// The emergency thread takes an element out of the Bq, leaving a queue empty, and the last blocking thread can complete the put
new Thread(()->{
try {
blockingQueue.take();
System.out.println("Open another one."+Thread.currentThread().getName()+"Take it out, can the blocking thread output? You can");
} catch(InterruptedException e) { e.printStackTrace(); }},"Emergency thread").start();
System.out.println("Can the main thread output? Can output, no impact, because the blocking thread is the one above the blocking thread.");
System.out.println("If put on the main thread, it will block here as well \n====================");
}
Copy the code
Because put() and take() use the Condition monitor, calls to await and single achieve precise sleep and wakeup
Here’s the parsing
The member variable condition
Mentioned above, not repeated here
private final Condition notFull;
Put method
While (count == items.length) queue is full, notfull.await (); Thread waiting
Notempty.signal () in enqueue; Wake up the blocked Take thread
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally{ lock.unlock(); }}private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
Copy the code
Take method
While (count == 0) queue empty, notempty.await (); Thread waiting
In the dequeue method, notfull.signal (); Wake up the blocked PUT thread
public E take(a) throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally{ lock.unlock(); }}private E dequeue(a) {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] ! = null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if(itrs ! =null)
itrs.elementDequeued();
notFull.signal();
return x;
}
Copy the code
4. Wait time out
The analogy with blocking and waiting is easy to understand
Blocking wait means that the queue will wait until another thread is operating on it
Timeout wait Waits for a specified period of time. Timeout is abandoned
//4
public static void outTimeWait(a) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
// The offer and poll methods are used again, but this time with parameters
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
System.out.println("Normal offer method, immediately abandon ->"+blockingQueue.offer("d"));
// Wait 3 seconds, if the block is still blocked, abort
// If the method takes no parameters, it will be abandoned immediately
System.out.println("Offer method with input, wait and discard ->"+blockingQueue.offer("d".3,TimeUnit.SECONDS));
blockingQueue.poll();
blockingQueue.poll();
blockingQueue.poll();
System.out.println("Normal poll method, discard immediately ->" + blockingQueue.poll());
// Wait 3 seconds, still blocked, aborted
System.out.println("Poll method with parameters, wait and abandon ->" + blockingQueue.poll(3,TimeUnit.SECONDS));
}
Copy the code
Let’s look at the source code with parameter offer
long nanos = unit.toNanos(timeout); The timeout was obtained
If (nanos <= 0) determines whether the timer ends
- Nacos <0, countdown ends,
return false;
Give up waiting to come straight back (disheartened) - Nacos >0 and counting
nanos = notFull.awaitNanos(nanos);
Call condition’s awaitNanos to continue the timed wait (hopefully)
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally{ lock.unlock(); }}Copy the code
SynchronizedQueue
Unlike other BlockingQueues, it is not used to store elements
Once an element is put, it must be taken out, or else it waits (equivalent to a one-space BlockingQueue?).
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @DescriptionUnlike other BlockingQueues, synchronous queues are not used to store elements. Once an element is put, it must be taken out, or else it will wait. *@Date 2021-11-06 下午 04:42
*/
public class SynchronizedQueueTest {
public static void main(String[] args) {
BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put a");
synchronousQueue.put("a");
System.out.println(Thread.currentThread().getName()+"put b");
synchronousQueue.put("b");
System.out.println(Thread.currentThread().getName()+"put c");
synchronousQueue.put("c");
} catch(InterruptedException e) { e.printStackTrace(); }},"Thead-put").start();
new Thread(()->{
try {
// Give him some time to put
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"take = " + synchronousQueue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"take = " + synchronousQueue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"take = " + synchronousQueue.take());
} catch(InterruptedException e) { e.printStackTrace(); }},"Thread-take").start(); }}Copy the code
The thread pool
Normally, threads need to be created and destroyed. It’s a waste of resources and time
Pooling technology: prepare some resources in advance, and if someone wants to use them, come to me to take them and return them to me after using them
Benefits of thread pools:
- Reduce resource consumption
- Increase the speed of response
- Convenient management
Thread reuse, can control the maximum number of concurrent, management threads
Thread pool: 3 methods, 7 parameters, 4 rejection policies **
Ali Java specification about thread pools writes
Thread pools are not allowed to be created by Executors, but by ThreadPoolExecutor
In this way, students can be more clear about the running rules of the thread pool and avoid the risk of resource exhaustion.
* If the thread pool object returns by Executors, it has the following disadvantages:
FixedThreadPool
andSingleThreadPool
:
- The allowed request queue length is
Integer.MAX_VALUE
, may pile up a large number of requests, resulting in OOMCachedThreadPool
andScheduledThreadPool
:
- The number of threads allowed to create is
Integer.MAX_VALUE
, may create a large number of threads, resulting in OOMOOM: Out of Memory Memory overflow
Executors are also new’s ThreadPoolExecutor, but specify some parameters by default
Three methods
1. SingleThreadExecutor
//Single creates a Single thread for processing
ExecutorService service1 = Executors.newSingleThreadExecutor();
try{
for(int i=1; i<=5; i++){ service1.execute(()->{ System.out.println(Thread.currentThread().getName()+"In execution"); }); }}catch(Exception e){
e.printStackTrace();
}finally{
service1.shutdown();
TimeUnit.SECONDS.sleep(1);
System.out.println("========== service1 closed ==========");
}
Copy the code
The execution result
Pool-1-thread-1 Executing pool-1-thread-1 Executing pool-1-thread-1 Executing pool-1-thread-1 Executing pool-1-thread-1 Executing Pool-1-thread-1 Executing Pool-1-thread-1 Executing
FixedThreadPool FixedThreadPool
//Fix fixed, according to the argument nThreads, to create a fixed thread pool size
ExecutorService service2 = Executors.newFixedThreadPool(5);
try{
for(int i=1; i<=10; i++){ service2.execute(()->{ System.out.println(Thread.currentThread().getName()+"In execution"); }); }}catch(Exception e){
e.printStackTrace();
}finally{
service2.shutdown();
TimeUnit.SECONDS.sleep(1);
System.out.println("========== service2 closed ==========");
}
Copy the code
The execution result
Pool-2 thread-2 Pool-2 thread-2 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-1 Pool-2 thread-3 Pool-2 thread-4 Pool-2 thread-5 Pool-2 thread-5
3, Cache thread pool CachedThreadPool
// Retractable, strong when strong, weak when weak
ExecutorService service3 = Executors.newCachedThreadPool();
try{
for(int i=1; i<=10; i++){// All pool-3-thread-1 threads are executed
// It is possible to infer that multiple threads are pooled only when there is a large number of concurrent requests
// Thread.sleep(1);
service3.execute(()->{
System.out.println(Thread.currentThread().getName()+"In execution"); }); }}catch(Exception e){
e.printStackTrace();
}finally{
service3.shutdown();
TimeUnit.SECONDS.sleep(1);
System.out.println("========== service3 closed ==========");
}
Copy the code
The execution result
Pool in carrying out the pool – 3 – thread – 1-3 – thread – 2 in the execution of the pool – 3 – thread – 3 in carrying out the pool – 3 – thread – in the execution of the pool – 3-4 thread – 5 in the execution of the pool – 3 – thread – 7 in the execution Pool-3-thread-8 Pool-3-thread-9 Pool-3-thread-2 Pool-3-thread-6 Pool-3-thread-6 Pool-3-thread-8 Pool-3-thread-9 Pool-3-thread-2 Pool-3-thread-6
Seven parameters
If you look at the source code, you can see that one of the three methods that call Executor is the ThreadPoolExecutor of New
public static ExecutorService newSingleThreadExecutor(a) {
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(a) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Copy the code
They just default to some fixed parameter, such as maximumPoolSize= integer.max_value
As the saying goes, what fits is best, and sometimes the default argument is not always the right one, so the Ali Java specification lets us call the native thread pool to help classes create a thread pool
public ThreadPoolExecutor(intCorePoolSize, // The initial core thread pool sizeintMaximumPoolSize, // Maximum thread pool size (not enough core threads, add non-core threads)longKeepAliveTime, // keepAliveTime, // keepAliveTime, // keepAliveTime, // keepAliveTime. BlockingQueue<Runnable> workQueue,// ThreadFactory ThreadFactory,// ThreadFactory, Use the default RejectedExecutionHandler handler.AbortPolicy is used when the request exceeds the maximum thread capacity and the blocking queue is full.
Copy the code
Description of thread pool operation principle
As you can see, when a request is made to the thread pool, the thread pool first processes the request according to the core thread created at initialization. When the core thread is in use, subsequent requests are put into the blocking queue
When the core threads are all processing and the blocking queue is full, non-core threads continue to be created based on maximumPoolSize, the maximum thread pool size
KeepAliveTime and Unit determine how long non-core threads can live without business calls
Non-core threads are retracted after keepAliveTime ends
If all threads (core and non-core) are taken and the blocking queue is full, the rejection policy is adopted
The default rejection policy is AbortPolicy, which does not accept anything beyond the tolerance and throws an exception
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
/ / RejectedExecutionHandler class is the interface class, there are four implementation class, we call it four refused to strategy
public static class CallerRunsPolicy implements RejectedExecutionHandler
public static class AbortPolicy implements RejectedExecutionHandler
public static class DiscardPolicy implements RejectedExecutionHandler
public static class DiscardOldestPolicy implements RejectedExecutionHandler
Copy the code
Simple code implementation
We initialize two core threads with a maximum of five, a timeout of five seconds, a blocking queue size of three, a default thread factory, and an abort policy
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @DescriptionCall native ThreadPoolExecutor to create a thread pool *@Date 2021-11-07 下午 03:30
*/
public class MyPool {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2.5.5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try{
for(int i=1; i<=9; i++){ threadPoolExecutor.execute(()->{ System.out.println(Thread.currentThread().getName()+"In operation"); }); }}catch(Exception e){
e.printStackTrace();
}finally{ threadPoolExecutor.shutdown(); }}}Copy the code
The results show that there are two core threads running at this time
Because 3 out of 5 requests are put in the blocking queue
Pool-1-thread-1 Is running Pool-1-thread-2 is running Pool-1-thread-1 is running Pool-1-thread-2 is running Pool-1-thread-1 is running
At this point, if we increase the number of loops to 8, we can see that 3,4,5 more threads have been created, which is the maximum number of non-core threads (5-2=3), which means that 3 more non-core threads can be created to solve the problem
Pool-1-thread-2 Is running Pool-1-thread-5 is running Pool-1-thread-3 is running Pool-1-thread-5 is running Pool-1-thread-1 is running Pool-1-thread-2 is running Pool-1-thread-3 Is running Pool-1-thread-4 is running
You know, we have a maximum of five threads and three positions in the queue, so we can have a total of eight requests, so what happens when we loop nine times?
Pool-1-thread-2 Is running Pool-1-thread-4 is running Pool-1-thread-3 is running Pool-1-thread-1 is running Pool-1-thread-3 is running Pool-1-thread-4 is running – the thread pool – 1-5 are running – the thread pool – 1-2 is running Java. Util. Concurrent. RejectedExecutionException story (omitted)
Yes, when the concurrent request processing not to come over, we select the refuse strategy is directly refuse the request and AbortPolicy suspended RejectedExecutionException exception
Four rejection strategies
1. AbortPolicy AbortPolicy
When concurrent requests processing not over AbortPolicy suspension strategies will directly discarded tasks and throw an exception. Java util. Concurrent. RejectedExecutionException
2. The caller runs CallerRunsPolicy
Go back to where you came from
The thread pool says, “I don’t have the resources to process your request anymore. Whoever let you in will do it.
Pool-1-thread-1 Running Main Running Pool-1-thread-1 Running Pool-1-thread-3 Running Pool-1-thread-2 Running Pool-1-thread-3 running Pool-1-thread-1 Is running Pool-1-thread-5 is running Pool-1-thread-4 is running
3, DiscardPolicy
If there are not enough resources, drop the task directly without throwing an exception.
DiscardOldestPolicy DiscardOldestPolicy
Discard old tasks in the thread and add new ones
Delete the first queued task and try to join the queue later
When a task is rejected, the oldest task in the queue is discarded and the new task is added to the queue
In the rejectedExecution command, remove the task to be added first from the task queue, leave a position vacant, and execute the execute method again to add the task to the queue
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
Copy the code
How to define the maximum number of threads?
CPU bound
CPU intensive, also called computing intensive, refers to the system hard disk and memory performance is much better than CPU
At this time, most of the system operation is CPU Loading100%, THE CPU needs to read/write I/O(disk/memory), I/O can be completed in a very short time, but the CPU has a lot of operations to process, CPU Loading is very high. On a multiprogramming system, a program that spends most of its time doing CPU actions, such as calculations and logical decisions, is called CPU bound
Cpu-bound programs tend to use a lot of CPU. This may be because the task itself does not require much access to the I/O device, or because the program is multithreaded and therefore blocks out waiting for I/O
Generally, the number of threads is set to:
** Number of threads = number of CPU cores +1 **(Modern cpus support hyperthreading, using idle wait)
I/O bound
I/O intensive means that the CPU performance of the system is much better than that of hard disks and memory. In this case, the CPU is waiting for I/ OS (hard disks and memory) to read or write, and the CPU Loading is not high.
I/O Bound’s programs tend to hit their performance limits and still have low CPU usage. This is probably because the task itself requires a lot of I/O operations and the Pipeline didn’t do a very good job of utilizing processor power.
Generally, the number of threads is set to:
** Number of threads = Total CPU cores * 2 +1 **
The maximum number of processors available for a Java virtual machine must not be less than one
Runtime.getRuntime().availableProcessors()
Copy the code
You can also view it in Task Manager -> Performance -> CPU -> Logical Processor
Four functional interfaces
Programmers of the new era need to master: lambda expressions, chain programming, functional interfaces, Stream computing
What is a functional interface
An interface that has only one method
Typical examples are Runnable and Callable interfaces
You can see that they are annotated by @functionalinterface, which is literally called a FunctionalInterface
@FunctionalInterface
public interface Runnable {
public abstract void run(a);
}
@FunctionalInterface
public interface Callable<V> {
V call(a) throws Exception;
}
Copy the code
1, Function interface
I have an input T and an output R
@FunctionalInterface
public interface Function<T.R> {
R apply(T t);
}
Copy the code
// Plain calls, using anonymous inner classes
Function<String,String> function=new Function<String, String>() {
@Override
public String apply(String s) {
returns; }}; System.out.println(function.apply("test"));
//lambda
Function<String,String> functionL= (str)->{returnstr; }; System.out.println(functionL.apply("lambda"));
Copy the code
It could even be easier
/ / more easily
Function<String,String> functionLS= str-> str;
System.out.println(functionLS.apply("simple"));
Copy the code
2. Predicate interfaces
If there is input, return Boolean
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Copy the code
Predicate<Integer> predicate = new Predicate<Integer>(){
@Override
public boolean test(Integer num) {
return num.equals(1); }}; System.out.println(predicate.test(2));
System.out.println(predicate.test(1));
Predicate<Integer> predicateL= num-> num.equals(1);
System.out.println("predicateL.test(1) = "+predicateL.test(1));
Copy the code
3. Consumer interface
Only input, no return value
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Copy the code
Sout can even simplify system.out ::println
Consumer<String> consumer= str-> System.out.println(str);
consumer.accept("out");
// Minimal version
Consumer<String> consumerS= System.out::println;
consumerS.accept("out");
Copy the code
4. Supplier interface Supplier
Only return values, no arguments
@FunctionalInterface
public interface Supplier<T> {
T get(a);
}
Copy the code
Supplier<String> supplier= ()-> "asd";
System.out.println(supplier.get());
Copy the code
Stream computing
Is a stream an IO stream?
** is not!! ** This is an IO stream under the java.io package
The java.util package contains a Stream
import java.io.InputStream;
import java.util.stream.Stream;
Copy the code
What is Stream computing?
Big data = computing + storage
Storage: collections, mysql databases…
Calculation: stream
And there are many, many arguments in these streams that use functional interfaces
Without further ado, get right to the code
import java.util.Arrays;
import java.util.List;
/ * * *@Author if
* @DescriptionThere are now 5 users to filter: * 1, ID must be even * 2, age must be greater than 23 * 3, username uppercase * 4, username alphabeticssorted backwards * 5, output only one user *@Date 2021-11-07 下午 06:12
*/
public class Test01 {
public static void main(String[] args) {
User user1 = new User(1."a".21);
User user2 = new User(2."b".22);
User user3 = new User(3."c".23);
User user4 = new User(4."d".24);
User user5 = new User(6."e".25);
// Add five users to the list
List<User> list= Arrays.asList(user1,user2,user3,user4,user5);
// The calculation is passed to the stream
list.stream()
//ID must be even
.filter(user-> user.getId()%2= =0)
// Must be older than 23
.filter(user-> user.getAge()>23)
// Change the user name to uppercase
.map(user -> user.getName().toUpperCase())
// User name alphabetically sorted backwards (comparator.reverseOrder ())
.sorted((u1,u2)->u2.compareTo(u1))
// Output only one user
.limit(1) .forEach(System.out::println); }}Copy the code
ForkJoin branch merge calculation
What is ForkJoin?
ForkJoin performs tasks in parallel in JDK1.7 for increased efficiency and large data volumes! Big Data: Map Reduce (Breaking big tasks into smaller ones)
ForkJoin nature: Divide and conquer
A big task is divided into many small tasks, and finally the results are summarized to get the solution
ForkJoin features: Work to steal
When THREAD B finishes earlier and A is not finished yet, B will take some work from A’s task to help share the pressure and improve efficiency
These are all two-ended queues that are maintained
How do I use ForkJoin
It is better to use it in the case of large data volume to improve efficiency, and it is better to directly use for loop in the case of small data volume
Direct access to the code, which feels like a recursion, adds the work queue mechanism of ForkJoin
There are three calls
- public void execute(ForkJoinTask
task), called directly with no return value - Public ForkJoinTask Submit (ForkJoinTask task)Task executionTo return toGet ForkJoinTask class
- Public final V get(), which returns the ForkJoinTask get method to get the result
- Public T invoke(ForkJoinTask task), invoke is executed
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
/ * * *@Author if
* @Description: What is it
* @Date 2021-11-07 下午 06:46
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long start=1L;
long end=10_0000_0000L;
ForkJoinDemo forkJoinDemo = new ForkJoinDemo(start,end);
ForkJoinPool forkJoinPool=new ForkJoinPool();
// Execute the task, no return value
// forkJoinPool.execute(forkJoinDemo);
ForkJoinTask submits the task, obtains the return value of the ForkJoinTask class, and then retrieves the result based on the class get
// ForkJoinTask
submit = forkJoinPool.submit(forkJoinDemo);
// System.out.println("submit.get() = " + submit.get());
// Invoke is invoked to get the return value directly, which is simpler than the previous method
Long sum = forkJoinPool.invoke(forkJoinDemo);
System.out.println("sum = " + sum);
}
private Long start;
private Long end;
private Long temp=10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute(a) {
// When the value is smaller than the critical value, a normal loop is taken
if((end-start)<temp){
long sum=0L;
for(longi=start; i<=end; i++){ sum+=i; }return sum;
}
// When the threshold is exceeded, fork-join is used
long mid=(start+end)/2;
// Divide it into two tasks
ForkJoinDemo fj1=new ForkJoinDemo(start,mid);
// Press the task queue
fj1.fork();
ForkJoinDemo fj2=new ForkJoinDemo(mid+1,end);
fj2.fork();
// Returns the result of the integration
returnfj1.join()+fj2.join(); }}Copy the code
You can also use stream to parallel streams
// Use stream to parallel streams
Long sum = LongStream.rangeClosed(start, end)
.parallel()
.reduce(0,Long::sum);
System.out.println("sum = "+sum1);
Copy the code
The asynchronous call
We use two methods of the CompletableFuture class, runAsync() and supplyAsync()
public class CompletableFuture<T> implements Future<T>, CompletionStage<T>
Copy the code
An asynchronous call with no return value
// runAsync asynchronous call with no return value
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
System.out.println("CompletableFuture.runAsync");
});
runAsync.get();
System.out.println("= = = = = = = = = = = = = =");
Copy the code
An asynchronous call with a return value
Similar to Ajax and AXIos, the requested business is submitted with a success callback and a failure callback
The argument to the supplyAsync() method is a functional interface Supplier Supplier
@FunctionalInterface
public interface Supplier<T> {
T get(a);
}
Copy the code
That is, where the business logic is executed
The processing of the business can then be determined by the success callback whenComplete and failure callback
The code is as follows, and the comments are very clear, so it’s arranged here to make it easier to see
- The successful callback whenComplete is executed with or without an exception, so judge t and u to determine the code logic
- If t is not null, the execution will be normal. If t is null, the execution will fail.
- U is an error message. If u is null, it is normal. Otherwise, it is an exception message
- No return value (because the return value 200 for successful business execution was already written in supplyAsync)
- The failure callback is exceptionally, which is executed only if an exception occurs
- Parameter e, usually Exception e
- There are return values (usually different return values depending on the exception)
// A runAsync asynchronous call with a return value
// Return a CompletableFuture object, and get the final result
CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> {
// Execute the business logic
System.out.println("CompletableFuture. Execute the business logic in the supplyAsync");
// int i=1/0;
return 200;
})
// The successful callback is executed regardless of the exception, so judge t and u to determine the code logic
.whenComplete((t, u) -> {
// If t is not null, it is executed normally
if(! Objects.isNull(t)&&Objects.isNull(u)){ System.out.println("t = " + t);// Return a normal result
}else{
// If u is null, the execution is normal; otherwise, an exception message is displayed
System.out.println("Exception message u =" + u);// Error message}})// The failure callback is executed only if an exception occurs
.exceptionally((e) -> {
// The parameter is Exception e
// e.printStackTrace();
System.out.println("Exception callback -> LLDB etMessage() =" + e.getMessage());
return 400;
});
// Get successful/exception results
System.out.println("result.get() = "+result.get());
Copy the code
The execution result
Normal execution
CompletableFuture. Execute the business logic in the supplyAsync t = 200. The result of the get () = 200
Execution failure
CompletableFuture. Execute the business logic in the supplyAsync exception information u = java.util.concurrent.Com pletionException: Java. Lang. ArithmeticException: / by zero anomaly correction – > um participant etMessage () = Java lang. ArithmeticException: / by zero
result.get() = 400
Understand the JMM
We’ve been dealing with JVMS, Java Virtual Machines, Java Virtual Machines
What is the JMM?
Java Memory Model
It is a nonexistent model, equivalent to a concept, a convention
Some synchronization conventions for the JMM
- The shared variable == must be flushed back to main memory immediately before the thread can be unlocked
- Before a thread locks, it must read the latest value from main memory into working memory!
- Locking and unlocking are the same lock
JVM design takes into account that if JAVA threads directly manipulate main memory each time they read and write variables, performance will be greatly affected. Therefore, each thread has its own working memory. Variables in working memory are a copy of main memory, and the thread reads and writes variables directly in working memory. You can’t directly manipulate variables in main memory. The problem with this, however, is that when a thread changes a variable in its own working memory, it is not visible to other threads, leading to thread-unsafe problems. Because the JMM has a set of standards to ensure that developers, when writing multithreaded programs, can control when memory is synchronized to other threads. Right
Let’s take a look at what the following two threads do to main memory
Memory interoperation
There are eight types of memory interaction operations, and virtual machine implementations must ensure that each operation is atomic and non-separable
(Exceptions are allowed for load, store, read, and write on some platforms for variables of type double and long.)
- Lock: A variable that acts on main memory, marking a variable as thread-exclusive
- Unlock: A variable that acts on main memory. It releases a locked variable so that it can be locked by another thread
- Read: Acts on a main memory variable that transfers the value of a variable from main memory to the thread’s working memory for subsequent load action
- Load: Variable acting on working memory, which puts a read operation variable from main memory into working memory
- Use: Applies to variables in working memory. It transfers variables in working memory to the execution engine. This instruction is used whenever the virtual machine reaches a value that requires the variable to be used
- Assign: A variable applied to working memory that places a value received from the execution engine into a copy of the variable in working memory
- Store: Acts on a variable in main memory. It transfers a value from a variable in working memory to main memory for subsequent writes
- Write: A variable in main memory that puts the value of a variable in main memory from the store operation in working memory
The JMM lays down the following rules for the use of these eight directives:
- One of the read and load, store and write operations is not allowed to occur separately
- If you use read, you must load; if you use store, you must write
- Disallow a thread to discard its latest assign operation
- That is, the main memory must be notified when the data of the working variable changes
- A thread is not allowed to synchronize unassigned data from working memory back to main memory
- A new variable must be created in main memory. Working memory is not allowed to use an uninitialized variable directly
- Before performing use and store operations on variables, you must pass assign and load operations
- Only one thread can lock a variable at a time. You must unlock the device for the same number of times
- If a variable is locked, all of its values in working memory will be emptied. Before the execution engine can use the variable, the variable must be reloaded or assigned to initialize its value
- You cannot unlock a variable if it is not locked. You cannot unlock a variable that is locked by another thread
- Before an UNLOCK operation can be performed on a variable, it must be synchronized back to main memory
Volatile
Volatile is a Java keyword that provides a lightweight synchronization mechanism for Java virtual machines, but Volatile does not guarantee thread safety
- Guaranteed visibility
- Atomicity is not guaranteed
- Disallow command reordering
1. Ensure visibility
Let’s look at this problem code
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @Description: What is it
* @Date 2021-11-08 下午 06:11
*/
public class JmmTest {
private static int num=0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0){
}
System.out.println("Thread terminated");
}).start();
TimeUnit.SECONDS.sleep(1);
num=1;
System.out.println("num = "+num); }}Copy the code
Will the “thread” end statement be printed at this point? Will not be
Because the thread does not know that num has been changed by the main thread, num in working memory is still 0, so it keeps looping
What if we added the keyword volatile to num?
private static volatile int num=0;
As you can see, the thread is finished
Num = 1
Process finished with exit code 0
2. Atomicity is not guaranteed
ACID refers to four characteristics that a database management system (DBMS) must have in order to ensure that a transaction is correct and reliable when data is written or updated:
- atomic(Atomicity, or indivisibility)
- All operations in a transaction either complete or do not complete and do not end up somewhere in the middle
- consistency(consistency)
- The integrity of the database is not compromised before and after a transaction
- Isolation,(Isolation, also called independence)
- The ability of a database to allow multiple concurrent transactions to read, write, and modify its data at the same time, and isolation prevents data inconsistencies due to cross-execution of multiple concurrent transactions **
- Transaction isolation can be divided into different levels, including Read uncommitted, Read Committed, Repeatable Read and Serializable.
- persistence(durability)
- After a transaction, changes to the data are permanent and will not be lost even if the system fails
Let’s look at the following code
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/ * * *@Author if
* @Description: What is it
* @Date 2021-11-08 下午 06:27
*/
public class VolatileTest {
private static int num=0;
private static volatile int vnum=0;
public static void add(a){
num++;
vnum++;
}
private static int snum=0;
public synchronized static void sAdd(a){
snum++;
}
private static int lnum=0;
private static Lock lock=new ReentrantLock();
public static void lAdd(a){
lock.lock();
try{
lnum++;
}catch(Exception e){
e.printStackTrace();
}finally{ lock.unlock(); }}public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
sAdd();
lAdd();
}
}).start();
}
while (Thread.activeCount()>2) {// The thread cedes the current time slice to another thread
Thread.yield();
}
System.out.println("End, ordinary num ="+num+", volatile vnum ="+vnum);
System.out.println("End,synchronized snum ="+snum);
System.out.println("End,lock method lnum ="+lnum); }}Copy the code
It can be seen that neither normal num nor volatile vnum is the correct 20000 result
Lnum and snum in lock and synchronized methods are correct with 20,000 results
Num = 19986; vnum = 19986; snum = 20000; lNUM = 20000
Because volatile does not guarantee atomicity, num++ increment is not an atomic operation
Decompile view
The javap -c volatiletest. class command is used to decompile the class file
public static void add(a);
Code:
// Get static num at the top of the stack
0: getstatic #2 // Field num:I
// Put constant 1 at the top of the stack
3: iconst_1
(num=num+1);
4: iadd
// The result at the top of the stack is assigned to static num
5: putstatic #2 // Field num:I
// Select * from vnum
8: getstatic #3 // Field vnum:I
11: iconst_1
12: iadd
13: putstatic #3 // Field vnum:I
16: return
Copy the code
Atomic classes Atomic
How do you implement atomicity without using synchronized and lock?
Using Java. Util. Concurrent. Atomic package under the atomic classes
The bottom layers of these classes hook directly to the operating system! Modify values in memory! The Unsafe class is a very special existence!
The code for
import java.util.concurrent.atomic.AtomicInteger;
/ * * *@Author if
* @Description: What is it
* @Date2021-11-08 11:12 p.m. */
public class AtomicTest {
private static AtomicInteger num=new AtomicInteger(0);
public static void add(a){
// Increment and return, CAS optimistic lock
num.getAndIncrement();
}
public static void main(String[] args) {
long startTime=System.currentTimeMillis();
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
long endTime=System.currentTimeMillis();
System.out.println("Program runtime:"+(endTime-startTime)+"ms");
// Get the value of num and print it
System.out.println("num.get() = "+num.get()); }}Copy the code
The results normally reach 20000 and the efficiency is not low
Num.get () = 20000
Let’s take a look at synchronized and lock
Program running time: 46ms end,synchronized method snUM = 20000
Program running time: 47ms end, LOCK method lnum = 20000
Now all three seem to be working pretty well
When we bring up the number of cycles
for (int i = 0; i < 20000; i++) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
//do something
}
}).start();
}
Copy the code
Program running time: 2136ms end,synchronized method snUM = 200000000
Program running time: 6287ms end, LOCK method lnum = 200000000
Num.get () = 200000000
Synchronized is dominant, atomic is second, and lock is the longest
3. Forbid instruction rearrangement
What is order reordering?
The program we write, the computer doesn’t do what you write
For performance reasons, the compiler and CPU may reorder instructions
What is instruction reordering: prioritizing certain instructions to improve efficiency without affecting results
Source code –> compiler optimizations –> instruction parallelism may also be rearranged –> memory systems –> execution
The as – if – serial semantics
The result of a single-threaded program cannot be changed, no matter how it is reordered. Okay
The compiler, runtime, and processor must comply with the AS-IF-Serial semantics
The processor considers dependencies between data when reordering instructions.
int x = 1; / / 1
int y = 2; / / 2
x = x + 5; / / 3
y = x * x; / / 4What we expect:1234But it may be executed back into2134 1324It can't be4123! Because y assignment depends on x!Copy the code
The instruction reordering can be avoided simply by adding volatile
Memory barriers are added to both reads and writes of memory areas: sequential exchange of instructions at rest
Function:
- Ensure the order in which certain operations are executed!
- Memory visibility of certain variables is guaranteed (visibility is achieved with these features, volatile)
Volatile can maintain visibility. Atomicity is not guaranteed. Thanks to the memory barrier, instruction reordering is guaranteed to be avoided!
The singleton pattern
For singleton patterns, check out my notes on singleton patterns
Understand the CAS
What is CAS?
CAS, the abbreviation of compare and swap, Chinese translation into compare and exchange
CAS is a concurrent primitive for the CPU and an atomic operation at the operating system level
We looked at an Unsafe class in the AtomicInteger class
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
Copy the code
As we know, Java can not operate the system directly, but through the keyword native C++ to operate the bottom layer of the system
The Unsafe class is Java’s “back door,” directly operating system memory
Before I talk about CAS, I want to look at the compareAndSet method, swap and assign
private volatile static AtomicInteger num=new AtomicInteger(0);
num.compareAndSet(1.2);
Copy the code
When num is 1, replace it with 2. CAS is similar to this
Let’s see how AtomicInteger increases atomicity in the previous section
private volatile static AtomicInteger num=new AtomicInteger(0);
public static void add(a){
// increment and return
num.getAndIncrement();
}
Copy the code
Then look at the getAndIncrement method
You can see the getAndAddInt method of the Unsafe class being called
private static final Unsafe unsafe = Unsafe.getUnsafe();
public final int getAndIncrement(a) {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
Copy the code
Looking at the source code for the broadening class’s getAndAddInt method
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
Copy the code
That is, we call this (AtomicInteger), valueOffset (memory address offset), and the value I that needs to be added
That’s basically what we did in the last section
Get static num and put it at the top of the stack
Put the constant 1 at the top of the stack
Add two values at the top of the current stack and place the result at the top of the stack (num=num+1)
The result at the top of the stack is assigned to static num
A simple explanation of the sample CAS source code
The value of the current element, var5, can be retrieved from the var1 instance object and its memory address, var2
It then loops through the CAS operation compareAndSwapInt to compare whether the var5 value has not changed and, if it is, to the new value
We also call this spin operation, or spin lock
Compares values in current working memory with values in main memory
If the value is expected, then the operation is performed!
If not, keep cycling!
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
Copy the code
If according tovar1
andvar2
The value that I took out, and the value that I took outvar5
Same, then I willvar5
Replace withvar5 + var4
And return
Method call native CAS primitive (var4 = 1)
The disadvantage of the CAS
- The loop takes time
- Only one shared variable is guaranteed atomicity at a time
- ABA problem
What are ABA problems?
Unbeknownst to the other threads, there comes a new leopard cat, but the other threads do not know that this has been replaced, nor do they know whether this leopard cat is still the original leopard cat
Let’s say A is equal to 1
Then thread B calls cas(1,3) and cas(3,1).
I’m going to replace the 1 with the 3, and then I’m going to replace the 3 with the 1
For thread A, the value of A is still 1, but it may not be the same 1
It doesn’t matter much for the base type because it points to the constant pool
If it is a reference type, there may be a problem. The value passed may not change, but the object does!
Solutions to ABA problems
Atomic operations with version numbers, see the next section, “Atomic References.”
Atomic reference
Optimistic locking can be implemented not only with CAS operations, but also with a version number mechanism
We use the AtomicStampedReference class to implement the version number mechanism
Note that the value of the compareAndSet method is equal to the value of the compareAndSet method. Therefore, the value of the Integer can only be used between -128 and 127!
static final int low = -128;
static final int high;
assert IntegerCache.high >= 127;
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Copy the code
Code implementation
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/ * * *@Author if
* @Description: Version number mechanism * Note that if Integer is used, only the range from -127 to 128 * is used because !!!!! is used at the bottom * expectedReference == current.reference * *@Date 2021-11-09 下午 04:33
*/
public class CASDemo {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1.1);
new Thread(()->{
// Get the version number
int stamp = atomicInteger.getStamp();
System.out.println("The original a-stamp =" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A -> "+atomicInteger.compareAndSet(1.2, stamp, (stamp + 1)));
System.out.println("A-stamp =" + atomicInteger.getStamp());
System.out.println("= = = = = = = = = = = = = = = = = = = = = = =");
},"A").start();
new Thread(()->{
int stamp = atomicInteger.getStamp();
System.out.println("B - stamp = " + stamp);
System.out.println("B -> "+atomicInteger.compareAndSet(1.2, stamp, stamp + 1));
System.out.println("B - stamp =" + atomicInteger.getStamp());
System.out.println("= = = = = = = = = = = = = = = = = = = = = = =");
},"B").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("GetStamp ="+atomicInteger.getStamp());
System.out.println("AtomicInteger value ="+atomicInteger.get(new int[]{atomicInteger.getStamp()})); }}Copy the code
The understanding of various locks
1. Fair locks and unfair locks
We should have seen this when we studied the Lock class
Lock lock=new ReentrantLock();
Let’s take a look at the constructor for ReentrantLock
// Create an unfair lock Nonfair by default
public ReentrantLock(a) {
sync = new NonfairSync();
}
// Boolean creates a Fair lock if the value is true, or Nonfair if the value is not true
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code
Fair Lock: Fair: First come first served Non-fair Lock: Unfair: Can cut in line (default)
2. Reentrant lock
Reentrant: a thread that has acquired a lock can acquire it again without deadlock
Synchronized and ReentrantLock are reentrant
- Implicit locks (that is, those used by the synchronized keyword) are reentrant by default
- Explicit locks (that is, locks) also have reentrantlocks
One of the points of reentrant locks is to prevent deadlocks
Of course, a lock() must also be unlocked () as many times as it can be unlocked
This is done by associating each lock with a request counter and a thread that owns it
When the count is 0, the lock is considered unused, and when a thread requests an unused lock, the JVM records the owner of the lock and sets the request counter to 1
If the same thread requests the lock again, the counter is incremented
The counter is decremented each time the occupying thread exits the synchronized block. Until the counter reaches 0, the lock is released
The default locks for the current phase are reentrant locks (also known as recursive locks)
If you want to achieve non-reentrant effects, you can set up your own class that inherits Lock
A member variable is bound to a thread. The first call assigns the current thread to the bound thread, and subsequent calls to lock determine whether the bound thread is the current thread. If the current thread is the bound thread, wait
For details, see this blog at blog.csdn.net/wb_zjp28312…
3. Spin locks
In fact, when WE talked about CAS, we talked about spin manipulation
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
Copy the code
“Spin” can be understood as “self-rotation,” where “spin” refers to a “loop,” such as a while loop or a for loop
“Spin” is the self repeating the cycle until the goal is achieved
Unlike normal locks, which block if the lock is not acquired
The main difference between a non-spin lock and a spin lock is that if it encounters a lock failure, it blocks the thread until it is woken up. And the spin lock will keep trying
The advantage of a spinlock is that it keeps the thread in a Runnable state by constantly trying to acquire the lock in a loop, which saves the overhead of thread state switching
However, if the critical area is large and the thread takes a long time to release the lock, then spin-locking is not suitable because the spin will always use up the CPU but can’t get the lock, wasting resources
4, a deadlock
What is a deadlock
Deadlock: In multithreading, a blockage caused by competing for resources or by communicating with each other that cannot proceed without external force
The system is said to be in a deadlock state or a deadlock occurs in the system. The processes that are always waiting for each other are called deadlocked processes
Deadlock conditions occur
- Must be two or more processes (threads)
- There must be competing resources
A picture with you understand deadlock!
Code sample
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @Description: Deadlock example *@Date 2021-11-09 下午 06:54
*/
public class DeadLockDemo {
public static void main(String[] args) {
Object lockA=new Object();
Object lockB=new Object();
new Thread(()->{
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"Get lock A");
try{
TimeUnit.SECONDS.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"Acquired lock B"); }}},"A").start();
new Thread(()->{
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"Acquired lock B");
try{
TimeUnit.SECONDS.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"Get lock A"); }}},"B").start(); }}Copy the code
The code should be clear, the middle sleep is for fear of a thread snatching two locks at the same time will not succeed
A grabs the lock of A and goes to sleep, B grabs the lock of B and goes to sleep, and THEN A wakes up and tries to get the lock of B, but of course he can’t get it. B also tries to get the lock of A, but of course he can’t get it either. All the locks needed are in the hands of the other party, so he naturally falls into deadlock
User A has obtained lock A. User B has obtained lock B
How to check for deadlocks?
- Locating process NUMBER:
- In a Windows command window, run the
jps -l
View the PID of the current Java process, through the package path is easy to distinguish their own program process
- In a Windows command window, run the
- Find thread status and problem code:
- View pid, enter
jstack -l 15528
, 15528 is the process PID
- View pid, enter
# check the process
>jps -l
14720
1464 org.jetbrains.jps.cmdline.Launcher
15528 com.ifyyf.test.deadlock.DeadLockDemo
4040 org.jetbrains.idea.maven.server.RemoteMavenServer36
9176 sun.tools.jps.Jps
# Check for specific error messages (too long, arbitrary)
>jstack -l 15528
Found one Java-level deadlock:
=============================
"B":
waiting to lock monitor 0x0000000002e39fe8 (object 0x000000076b614298, a java.lang.Object),
which is held by "A"
"A":
waiting to lock monitor 0x0000000002e3c928 (object 0x000000076b6142a8, a java.lang.Object),
which is held by "B"
Java stack information for the threads listed above:
===================================================
"B":
at com.ifyyf.test.deadlock.DeadLockDemo.lambda$mainThe $1(DeadLockDemo.java:42)
- waiting to lock <0x000000076b614298> (a java.lang.Object)
- locked <0x000000076b6142a8> (a java.lang.Object)
at com.ifyyf.test.deadlock.DeadLockDemo$$Lambda$2/1078694789.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"A":
at com.ifyyf.test.deadlock.DeadLockDemo.lambda$main$0(DeadLockDemo.java:26)
- waiting to lock <0x000000076b6142a8> (a java.lang.Object)
- locked <0x000000076b614298> (a java.lang.Object)
at com.ifyyf.test.deadlock.DeadLockDemo$$LambdaThe $1/990368553.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
Copy the code
End and spend
This JUC study is not particularly in-depth, it can be said that it is a simple door
This code is put in my Gitee warehouse, you can help yourself