Deep understanding of Java Concurrent lock

Code state Lord 2020-11-10 16:22:43
deep understanding java concurrent lock


In depth understanding of Java Concurrent locks

1. Introduction to concurrent lock

The most common way to ensure thread safety is to use the lock mechanism (Locksychronized) To synchronize shared data , So at the same time , Only one thread can execute a method or a block of code , So the operation must be atomic , Thread safe .

at work 、 During the interview , I often hear all kinds of locks , Listen to people in the clouds . There are many conceptual terms of lock , They are aimed at different problems , By simply combing , It's not hard to understand .

1.1. Reentrant lock

Reentrant lock , seeing the name of a thing one thinks of its function , It means that a thread can repeatedly acquire the same lock . That is, the same thread acquires the lock in the outer method , When entering the inner layer, the method will automatically acquire the lock .

Reentrant lock can avoid deadlock to some extent .

  • ReentrantLock 、ReentrantReadWriteLock  It's a reentrant lock . this , It's not hard to see from its name .
  • synchronized  It's also a reentrant lock .

【 Example 】synchronized  A reentrant example of

synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}

The above code is a typical scenario : If the lock used is not a reentrant lock ,setB  May not be executed by the current thread , And cause deadlock .

【 Example 】ReentrantLock  A reentrant example of

class Task {
private int value;
private final Lock lock = new ReentrantLock();
public Task() {
this.value = 0;
}
public int get() {
// Get the lock
lock.lock();
try {
return value;
} finally {
// Make sure the lock can be released
lock.unlock();
}
}
public void addOne() {
// Get the lock
lock.lock();
try {
// Be careful : The lock has been successfully acquired here , Get into get After the method , Try to get the lock again ,
// If the lock is not reentrant , Can cause deadlock
value = 1 + get();
} finally {
// Make sure the lock can be released
lock.unlock();
}
}
}

1.2. Fair lock and unfair lock

  • Fair lock  - Fair lock means   Multithreads acquire locks in the order in which they apply for locks .
  • Not fair lock  - Unfair lock means   Multithreading does not acquire locks in the order in which they are requested  . This may cause priority reversal ( Later generations live in ) Or hunger ( One thread can't compete with other threads , Resulting in never being able to execute ).

Fair lock to ensure thread application sequence , There must be a performance cost , Therefore, its throughput is generally lower than the unfair lock .

Fair lock and unfair lock stay Java Typical implementation of :

  • synchronized  Only unfair locks are supported .
  • ReentrantLock 、ReentrantReadWriteLock, Default is unfair lock , But support fair locks .

1.3. Exclusive lock and shared lock

Exclusive lock and shared lock are a kind of broad sense , In terms of practical use , It is also often called mutually exclusive lock and read-write lock .

  • Exclusive lock  - Exclusive lock means   Locks can only be held by one thread at a time .
  • Shared lock  - Shared lock means   Locks can be held by multiple threads .

Exclusive lock and sharing lock in Java Typical implementation of :

  • synchronized 、ReentrantLock  Only exclusive locks are supported .
  • ReentrantReadWriteLock  Its writing lock is exclusive lock , The read lock is a shared lock . Read locks are shared locks that make concurrent reads very efficient , Reading and writing , Write to read , The process of writing is mutually exclusive .

1.4. Pessimistic lock and optimistic lock

Optimistic lock and pessimistic lock are not specific lock types , It's a strategy for dealing with concurrent synchronization .

  • Pessimistic locking  - Pessimistic lock takes a pessimistic attitude towards concurrency , Think : Concurrent operations without locks are bound to go wrong . Pessimistic lock is suitable for frequent write operations .
  • Optimism lock  - Optimistic lock is optimistic about concurrency , Think : There's no problem with concurrent operations without locks . For concurrent operations of the same data , It won't change . When updating data , The data will be updated in a continuous attempt to update . Optimistic lock is suitable for reading more and writing less .

Pessimistic lock and optimistic lock in Java Typical implementation of :

  • Pessimism is locked in Java The application in is through the use of  synchronized  and  Lock  Show lock for exclusive synchronization , This is a blocking synchronization .

  • Optimism is locked in Java The application in is to use  CAS  Mechanism (CAS  The operation passes  Unsafe  Class provides , But this class is not directly exposed as API, So it's all indirect use , Like all kinds of atoms ).

1.5. Biased locking 、 Lightweight lock 、 Heavyweight lock

So called lightweight lock and heavyweight lock , It refers to the thickness of lock control granularity . obviously , The finer the control granularity , The less blocking overhead , The more concurrent it is .

Java 1.6 before , A heavyweight lock generally means  synchronized , And lightweight locks mean  volatile.

Java 1.6 in the future , in the light of  synchronized  A lot of optimization , introduce 4 Lock state : No lock state 、 Biased locking 、 Lightweight lock and heavyweight lock . Locks can be upgraded from biased locks to lightweight locks in one direction , Then upgrade from lightweight lock to heavyweight lock .

  • Biased locking  - Biased lock means that a piece of synchronous code is always accessed by a thread , Then the thread will automatically acquire the lock . Reduce the cost of lock acquisition .

  • Lightweight lock  - When a lock is biased toward a lock , Accessed by another thread , Biased locks will be upgraded to lightweight locks , Other threads try to acquire the lock by spinning , It won't block , Improve performance .

  • Heavyweight lock  - When the lock is a lightweight lock , The other thread is spinning , But spin doesn't last , When you spin a certain number of times , We haven't got the lock yet , It's going to go into a block , The lock expands to a heavyweight lock . Heavyweight locks will block other threads applying for it , Performance degradation .

1.6. Section lock

The sectional lock is actually a kind of lock design , It's not a specific kind of lock . The so-called sectional lock , It is to divide the object of the lock into segments , Each segment is controlled independently , Make the lock finer , Reduce blocking overhead , To improve concurrency . It's actually quite understandable , It's like a toll booth on the highway , If there is only one toll gate , Then all the cars can only line up to pay ; If there are multiple toll gates , You can divert .

Hashtable  Use  synchronized  Decorate methods to ensure thread safety , So the access to the thread ,Hashtable Will lock the whole object , All other threads can only wait , The throughput of this blocking mode is obviously very low .

Java 1.7 Former  ConcurrentHashMap  It's a typical case of segmented lock .ConcurrentHashMap  One was maintained  Segment  Array , It's generally called segmented barrel .

final Segment<K,V>[] segments;

When there is thread access  ConcurrentHashMap  When the data is ,ConcurrentHashMap  According to hashCode Figure out which bucket the data is in ( Which one Segment), Then lock this  Segment.

1.7. Display locks and built-in locks

Java 1.5 Before , The only mechanism you can use to coordinate access to shared objects is  synchronized  and  volatile. Both are built-in locks , That is to say, the application and release of locks are made by JVM Controlled by .

Java 1.5 after , New mechanisms have been added :ReentrantLockReentrantReadWriteLock , The application and release of such locks can be controlled by procedures , So it's often called a display lock .

Be careful : If you don't need to  ReentrantLockReentrantReadWriteLock  Advanced synchronization features provided , Use should be a priority  synchronized . For the following reasons :

  • Java 1.6 in the future ,synchronized  A lot of optimization has been done , Its performance has been compared with  ReentrantLockReentrantReadWriteLock  Basically flat .
  • In terms of trend ,Java The future is more likely to be optimized  synchronized , instead of  ReentrantLockReentrantReadWriteLock , because  synchronized  yes JVM Built in properties , It can perform some optimizations .
  • ReentrantLockReentrantReadWriteLock  Application and release of lock are controlled by procedure , If not used properly , May cause deadlock , It's very dangerous .

The following comparison shows the difference between the lock and the built-in lock :

  • Actively acquire lock and release lock
    • synchronized  Can't actively acquire lock and release lock . Get lock and release lock are JVM The control of the .
    • ReentrantLock  Can actively acquire lock and release lock .( If you forget to release the lock , A deadlock may occur ).
  • In response to interrupt
    • synchronized  Unresponsive interrupt .
    • ReentrantLock  Can respond to interruptions .
  • Timeout mechanism
    • synchronized  There is no timeout mechanism .
    • ReentrantLock  There's a timeout mechanism .ReentrantLock  Timeout can be set , Automatically release the lock after timeout , Avoid waiting for .
  • Support fair locks
    • synchronized  Only unfair locks are supported .
    • ReentrantLock  Support unfair lock and fair lock .
  • Is sharing supported
    • By  synchronized  A decorated method or block of code , Can only be accessed by one thread ( exclusive ). If the thread is blocked , Other threads can only wait
    • ReentrantLock  Can be based on  Condition  Flexible control of synchronization conditions .
  • Is read-write separation supported
    • synchronized  Read / write lock separation is not supported ;
    • ReentrantReadWriteLock  Read and write lock support , So as to separate the blocking read and write operations , Effectively improve concurrency .

2. Lock and Condition

2.1. Why introduce Lock and Condition

Concurrent programming , There are two core issues : One is mutual exclusion , That is, only one thread is allowed to access shared resources at the same time ; The other is synchronization , That is, how threads communicate with each other 、 Collaboration . These two problems , Management can be solved .Java SDK And the contract is approved Lock and Condition Two interfaces to implement the tube , among Lock Used to solve the problem of mutual exclusion ,Condition Used to solve synchronization problems .

synchronized It's an implementation of tube engineering , In that case , Why offer more Lock and Condition.

JDK 1.6 before ,synchronized It hasn't been optimized yet , Performance is much lower than Lock. however , Performance is not the introduction of Lock The most important factor of . The real key is :synchronized Improper use , There may be deadlock .

synchronized Deadlock cannot be avoided by breaking the non preemptive condition . as a result of synchronized When you apply for resources , If the application does not arrive , Thread is directly in blocking state , And the thread goes into a blocked state , You can't do anything , It can't release the resources already occupied by the thread .

With built-in locks  synchronized  The difference is ,Lock  A set of unconditional 、 Pollable 、 Timed and interruptible lock operation , All access locks 、 Releasing locks is an explicit operation .

  • Be able to respond to interruptions .synchronized The problem is , Hold lock A after , If you try to acquire a lock B Failure , Then the thread goes into a blocked state , Once a deadlock occurs , There's no chance to wake up a blocked thread . But if the thread in the blocked state can respond to interrupt signals , That is to say, when we send an interrupt signal to a blocked thread , Can wake it up , Then it has a chance to release the lock it once held A. This will destroy the conditions that cannot be seized .
  • Support timeout . If the thread does not acquire the lock for a period of time , It's not blocking , It returns an error , Then this thread also has a chance to release the lock it once held . This can also destroy the conditions that cannot be seized .
  • Non blocking access to locks . If the attempt to acquire the lock fails , It doesn't get stuck , I'm going straight back , Then this thread also has a chance to release the lock it once held . This can also destroy the conditions that cannot be seized .

2.2. Lock Interface

Lock  The interface of is defined as follows :

public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
  • lock() - Get the lock .
  • unlock() - Release the lock .
  • tryLock() - Attempt to acquire lock , Only if the lock is not held by another thread at the time of the call , To get the lock .
  • tryLock(long time, TimeUnit unit) - and  tryLock()  similar , The difference is only in time , If the lock is not acquired within the limited time , See it as a failure .
  • lockInterruptibly() - The lock is not held by another thread , And the thread is not interrupted , To get the lock .
  • newCondition() - Returns a binding to  Lock  On the object  Condition  example .

2.3. Condition

Condition The conditional variables in the pipe process model are realized .

As mentioned above  Lock  Interface There is one  newCondition()  Method to return a binding to  Lock  On the object  Condition  example .Condition  What is it? ? What's the role ? This section will explain one by one .

In a single thread , The execution of a piece of code may depend on a certain state , If the state conditions are not met , The code will not be executed ( Typical scenario , Such as :if ... else ...). In a concurrent environment , When a thread judges a state condition , Its state may be changed due to the operation of other threads , There needs to be a coordination mechanism to ensure that at the same time , Data can only be modified by one thread lock , And the modified data state is sensed by all threads .

Java 1.5 Before , Mainly to make use of  Object  Class  waitnotifynotifyAll  coordination  synchronized  To communicate between threads .

waitnotifynotifyAll  Need to cooperate with  synchronized  Use , Do not apply to  Lock. While using  Lock  The thread of , Communication with each other should use  Condition . This can be understood as , What kind of lock matches what kind of key . Built in lock (synchronized) With built-in conditional queues (waitnotifynotifyAll ), Explicit lock (Lock) With explicit conditional queues (Condition ).

Condition Characteristics of

Condition  The interface is defined as follows :

public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}

among ,awaitsignalsignalAll  And  waitnotifynotifyAll  Corresponding , Functions are similar . in addition to ,Condition  Compared to the built-in conditional queue ( waitnotifynotifyAll ), It provides more functions :

  • Each lock (Lock) There can be more than one  Condition, This means that there can be more than one lock state condition .
  • Support fair or unfair queue operations .
  • Supports interruptible conditional waiting , Related methods :awaitUninterruptibly() .
  • Support timed waiting , Related methods :awaitNanos(long) 、await(long, TimeUnit)awaitUntil(Date).

Condition Usage of

Here we use  Condition  To achieve a consumer 、 Producer mode .

Product class

class Message {
private final Lock lock = new ReentrantLock();
private final Condition producedMsg = lock.newCondition();
private final Condition consumedMsg = lock.newCondition();
private String message;
private boolean state;
private boolean end;
public void consume() {
//lock
lock.lock();
try {
// no new message wait for new message
while (!state) { producedMsg.await(); }
System.out.println("consume message : " + message);
state = false;
// message consumed, notify waiting thread
consumedMsg.signal();
} catch (InterruptedException ie) {
System.out.println("Thread interrupted - viewMessage");
} finally {
lock.unlock();
}
}
public void produce(String message) {
lock.lock();
try {
// last message not consumed, wait for it be consumed
while (state) { consumedMsg.await(); }
System.out.println("produce msg: " + message);
this.message = message;
state = true;
// new message added, notify waiting thread
producedMsg.signal();
} catch (InterruptedException ie) {
System.out.println("Thread interrupted - publishMessage");
} finally {
lock.unlock();
}
}
public boolean isEnd() {
return end;
}
public void setEnd(boolean end) {
this.end = end;
}
}

consumer

class MessageConsumer implements Runnable {
private Message message;
public MessageConsumer(Message msg) {
message = msg;
}
@Override
public void run() {
while (!message.isEnd()) { message.consume(); }
}
}

producer

class MessageProducer implements Runnable {
private Message message;
public MessageProducer(Message msg) {
message = msg;
}
@Override
public void run() {
produce();
}
public void produce() {
List<String> msgs = new ArrayList<>();
msgs.add("Begin");
msgs.add("Msg1");
msgs.add("Msg2");
for (String msg : msgs) {
message.produce(msg);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
message.produce("End");
message.setEnd(true);
}
}

test

public class LockConditionDemo {
public static void main(String[] args) {
Message msg = new Message();
Thread producer = new Thread(new MessageProducer(msg));
Thread consumer = new Thread(new MessageConsumer(msg));
producer.start();
consumer.start();
}
}

3. ReentrantLock

ReentrantLock  Class is  Lock  The concrete implementation of the interface , With built-in locks  synchronized  The same thing , It's a reentrant lock .

3.1. ReentrantLock Characteristics of

ReentrantLock  Its characteristics are as follows :

  • ReentrantLock  Provided with  synchronized  Same mutex 、 Memory visibility and reentrancy .
  • ReentrantLock  Support fair locks and unfair locks ( Default ) Two modes .
  • ReentrantLock  Realized  Lock  Interface , Support  synchronized  Flexibility that you don't have .
    • synchronized  Unable to interrupt a thread waiting to acquire a lock
    • synchronized  Can't wait endlessly while requesting a lock

3.2. ReentrantLock Usage of

I've learned about  ReentrantLock  Characteristics of , Next , Let's talk about its specific usage .

ReentrantLock Construction method of

ReentrantLock  There are two constructors :

public ReentrantLock() {}
public ReentrantLock(boolean fair) {}
  • ReentrantLock() - The default constructor initializes an unfair lock (NonfairSync);
  • ReentrantLock(boolean) - new ReentrantLock(true)  A fair lock will be initialized (FairSync).

lock and unlock Method

  • lock() -  Acquire lock unconditionally . If the current thread cannot acquire the lock , Then the current thread is not available for hibernation , Until the current thread gets the lock . If the lock is not held by another thread , Then acquire the lock and return immediately , Set the hold count of the lock to 1.
  • unlock() - Used to release locks .

:bell: Be careful : Please remember , Acquire lock operation  lock()  Must be in  try catch  Out of the block , And will release the lock operation  unlock()  Put it in  finally  Carry out in block , To ensure that the lock must be released , Prevent deadlock .

Example :ReentrantLock  Basic operation

public class ReentrantLockDemo {
public static void main(String[] args) {
Task task = new Task();
MyThread tA = new MyThread("Thread-A", task);
MyThread tB = new MyThread("Thread-B", task);
MyThread tC = new MyThread("Thread-C", task);
tA.start();
tB.start();
tC.start();
}
static class MyThread extends Thread {
private Task task;
public MyThread(String name, Task task) {
super(name);
this.task = task;
}
@Override
public void run() {
task.execute();
}
}
static class Task {
private ReentrantLock lock = new ReentrantLock();
public void execute() {
lock.lock();
try {
for (int i = 0; i < 3; i++) {
System.out.println(lock.toString());
// Query the current thread hold The number of times to stay in this lock
System.out.println("\t holdCount: " + lock.getHoldCount());
// Query the number of threads waiting to acquire this lock
System.out.println("\t queuedLength: " + lock.getQueueLength());
// Is it a fair lock
System.out.println("\t isFair: " + lock.isFair());
// Is it locked
System.out.println("\t isLocked: " + lock.isLocked());
// Whether the lock is held by the current thread
System.out.println("\t isHeldByCurrentThread: " + lock.isHeldByCurrentThread());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
}

Output results :

java.util.concurrent.locks.ReentrantLock@64fcd88a[Locked by thread Thread-A]
holdCount: 1
queuedLength: 2
isFair: false
isLocked: true
isHeldByCurrentThread: true
java.util.concurrent.locks.ReentrantLock@64fcd88a[Locked by thread Thread-C]
holdCount: 1
queuedLength: 1
isFair: false
isLocked: true
isHeldByCurrentThread: true
// ...

tryLock Method

Compared with unconditional access lock ,tryLock There is a better fault tolerance mechanism .

  • tryLock() -  Can poll for lock . If it works , Then return to true; If you fail , Then return to false. in other words , Whether this method succeeds or fails, it will return immediately , Can't get lock ( The lock has been acquired by another thread ) Not always waiting for .
  • tryLock(long, TimeUnit) -  The lock can be acquired regularly . and  tryLock()  similar , The only difference is that this method will wait for a certain amount of time when the lock cannot be acquired , If the lock is not acquired within the time limit , Just go back to false. If you get the lock at the beginning or during the waiting period , Then return to true.

Example :ReentrantLock  Of  tryLock()  operation

Modify... In the previous example  execute()  Method

public void execute() {
if (lock.tryLock()) {
try {
for (int i = 0; i < 3; i++) {
// A little ...
}
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " Lock acquisition failed ");
}
}

Example :ReentrantLock  Of  tryLock(long, TimeUnit)  operation

Modify... In the previous example  execute()  Method

public void execute() {
try {
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
for (int i = 0; i < 3; i++) {
// A little ...
}
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " Lock acquisition failed ");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " Acquire lock timeout ");
e.printStackTrace();
}
}

lockInterruptibly Method

  • lockInterruptibly() -  Interruptible access lock . The interruptible acquisition lock can maintain the response to the interrupt while acquiring the lock . Interruptible access lock is a little more complicated than other ways of access lock , You need two  try-catch  block ( If it is thrown during the lock acquisition operation  InterruptedException , Then you can use standard  try-finally  Lock mode ).
    • for instance : Suppose that two threads pass through at the same time  lock.lockInterruptibly()  When acquiring a lock , If a thread A Got the lock , The thread B Can only wait for . If at this time on the thread B call  threadB.interrupt()  Method to interrupt threads B The waiting process . because  lockInterruptibly()  An exception was thrown in the declaration of , therefore  lock.lockInterruptibly()  Must be on  try  Block or calling  lockInterruptibly()  The method declaration of  InterruptedException.

:bell: Be careful : When a thread gets a lock , Will not be  interrupt()  Method interrupt . Call alone  interrupt()  Method cannot interrupt a running thread , Only threads in blocked state can be interrupted . So when we pass  lockInterruptibly()  Method to acquire a lock , If the lock is not acquired , Only in the state of waiting , To respond to an interrupt .

Example :ReentrantLock  Of  lockInterruptibly()  operation

Modify... In the previous example  execute()  Method

public void execute() {
try {
lock.lockInterruptibly();
for (int i = 0; i < 3; i++) {
// A little ...
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " Interrupted ");
e.printStackTrace();
} finally {
lock.unlock();
}
}

newCondition Method

newCondition() - Returns a binding to  Lock  On the object  Condition  example .

3.3. ReentrantLock Principle

ReentrantLock The visibility of

class X {
private final Lock rtl =
new ReentrantLock();
int value;
public void addOne() {
// Get the lock
rtl.lock();
try {
value+=1;
} finally {
// Make sure the lock can be released
rtl.unlock();
}
}
}

ReentrantLock, Hold one internally volatile Member variables of state, When getting the lock , Can read and write state Value ; When you unlock , I can read and write state Value ( The simplified code is shown below ). in other words , In execution value+=1 Before , The program reads and writes once volatile Variable state, In execution value+=1 after , Read and write again volatile Variable state. According to the relevant Happens-Before The rules :

  1. Sequential rules : For threads T1,value+=1 Happens-Before Release lock operation unlock();
  2. volatile Variable rule : because state = 1 Read first state, So threads T1 Of unlock() operation Happens-Before Threads T2 Of lock() operation ;
  3. Transitive rules : Threads T1 Of value+=1 Happens-Before Threads T2 Of lock() operation .

ReentrantLock Data structure of

read  ReentrantLock  Source code , You can see that it has a core field :

private final Sync sync;
  • sync - Internal abstract class  ReentrantLock.Sync  object ,Sync  Inherited from AQS. It has two subclasses :
  • ReentrantLock.FairSync - Fair lock .
  • ReentrantLock.NonfairSync - Not fair lock .

Check the source code to find out ,ReentrantLock  Realization  Lock  An interface is actually a call  ReentrantLock.FairSync  or  ReentrantLock.NonfairSync  The realization of each , Here's not a list .

ReentrantLock Get lock and release lock

ReentrantLock Get lock and release lock interface , From the appearance , Is to call  ReentrantLock.FairSync  or  ReentrantLock.NonfairSync  The realization of each ; In essence , Is based on AQS The implementation of the .

It's easy to read the source code carefully :

Call directly AQS The interface method will not be repeated , The principle is [AQS Principle ](#AQS Principle ) I've explained it in a lot of space .

nonfairTryAcquire  The source code of the method is as follows :

// Both fair locks and unfair locks attempt to acquire locks using this method area
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// If the synchronization state is 0, Set it to acquires, And set the current thread as exclusive thread
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

The process is simple :

  • If the synchronization state is 0, Set the synchronization state to acquires, And set the current thread as exclusive thread , Then return true, Lock acquired successfully .
  • If the synchronization state is not 0 And the current thread is exclusive , Set the synchronization state to the current state value +acquires value , Then return true, Lock acquired successfully .
  • otherwise , return false, Lock acquisition failed .

Fair and non-fair locks

ReentrantLock This class has two constructors , One is the parameterless constructor , One is the introduction fair Constructor for parameter .fair The parameter represents the fair policy of the lock , If you pass in true It means that we need to construct a fair lock , Otherwise, it means to construct an unfair lock .

Locks all correspond to a waiting queue , If a thread does not get a lock , It's going to enter the waiting line , When a thread releases a lock , You need to wake up a waiting thread from the waiting queue . If it's a fair lock , The wake-up strategy is who waits long , Wake up who , Very fair ; If it's unfair lock , There is no guarantee of fairness , It is possible that the thread with a short waiting time will be awakened first .

lock Methods in fair lock and unfair lock :

The difference between them is only when applying for unfair lock , If the synchronization state is 0, Try setting it to 1, If it works , Directly set the current thread as the exclusive thread ; Otherwise, it's like a fair lock , call AQS Get exclusive lock method  acquire.

// Unfair lock implementation
final void lock() {
if (compareAndSetState(0, 1))
// If the synchronization state is 0, Set it to 1, And set the current thread as exclusive thread
setExclusiveOwnerThread(Thread.currentThread());
else
// call AQS Get exclusive lock method acquire
acquire(1);
}
// Fair lock implementation
final void lock() {
// call AQS Get exclusive lock method acquire
acquire(1);
}

4. ReentrantReadWriteLock

ReadWriteLock  It is suitable for reading more and writing less .

ReentrantReadWriteLock  Class is  ReadWriteLock  The concrete implementation of the interface , It is a re - entry read - write lock .ReentrantReadWriteLock  Maintain a pair of read-write locks , Separate the read and write locks , It can improve the concurrent efficiency .

Read-write lock , Not at all Java Language specific , It's a general technology that is widely used , All read-write locks follow the following three basic principles :

  • Allow multiple threads to read shared variables at the same time ;
  • Only one thread is allowed to write shared variables ;
  • If a write thread is performing a write operation , At this time, it is forbidden to read shared variables by the read thread .

An important difference between read-write lock and mutex lock is that read-write lock allows multiple threads to read shared variables simultaneously , Mutexes are not allowed , This is the key to the better performance of read-write locks than mutexes in read more write less scenarios . But the write operations of read-write locks are mutually exclusive , When a thread is writing shared variables , Other threads are not allowed to perform write and read operations .

4.1. ReentrantReadWriteLock Characteristics of

ReentrantReadWriteLock Its characteristics are as follows :

  • ReentrantReadWriteLock  It is suitable for reading more and writing less . If it's writing more and reading less , because  ReentrantReadWriteLock  Its internal implementation ratio  ReentrantLock  complex , The performance may be worse . If there is such a problem , Need specific analysis . because  ReentrantReadWriteLock  Read and write lock of (ReadLockWriteLock) It's all done  Lock  Interface , So replace it with  ReentrantLock  It's easier .
  • ReentrantReadWriteLock  Realized  ReadWriteLock  Interface , Support  ReentrantLock  Do not have the read-write lock separation .ReentrantReadWriteLock  Maintain a pair of read-write locks (ReadLockWriteLock). Separate the read and write locks , It can improve the concurrent efficiency .ReentrantReadWriteLock  The lock strategy of is : Allow multiple read operations to execute concurrently , But only one write operation is allowed at a time .
  • ReentrantReadWriteLock  It provides reentrant lock semantics for both read and write locks .
  • ReentrantReadWriteLock  Support fair locks and unfair locks ( Default ) Two modes .

ReadWriteLock  The interface is defined as follows :

public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
  • readLock - Returns the lock used for the read operation (ReadLock).
  • writeLock - Returns the lock used for the write operation (WriteLock).

The interaction between the read-write lock and the write lock can be realized in many ways ,ReadWriteLock  Some of the optional implementations of include :

  • Release priority  - When a write operation releases a write lock , And there are both read and write threads in the queue , Then read thread should be selected first 、 Write a thread , The first thread to make the request ?
  • Read thread queue jumping  - If the lock is held by a read thread , But there are write threads waiting , Then whether the newly arrived read thread can obtain access right immediately , Or should wait behind the write thread ? If the read thread is allowed to queue before the write thread , That will improve concurrency , But it may cause thread starvation .
  • Reentrant  - Whether the read lock and write lock are reentrant ?
  • Downgrade  - If a thread holds a write lock , Then whether it can obtain the read lock without releasing the lock ? This may degrade the write lock to read lock , At the same time, other write threads are not allowed to modify the protected resources .
  • upgrade  - Can a read lock be upgraded to a write lock prior to other waiting read and write threads ? Upgrade is not supported in most read-write lock implementations , Because if there is no explicit upgrade operation , It's easy to cause deadlock .

4.2. ReentrantReadWriteLock Usage of

I've learned about  ReentrantReadWriteLock  Characteristics of , Next , Let's talk about its specific usage .

ReentrantReadWriteLock Construction method of

ReentrantReadWriteLock  and  ReentrantLock  equally , There are also two construction methods , And the usage is similar .

public ReentrantReadWriteLock() {}
public ReentrantReadWriteLock(boolean fair) {}
  • ReentrantReadWriteLock() - The default constructor initializes an unfair lock (NonfairSync). In an unfair lock , The order in which threads acquire locks is uncertain . It is possible to downgrade a write thread to a read thread , But it is not allowed to upgrade the read thread to the write thread ( This can lead to deadlock ).
  • ReentrantReadWriteLock(boolean) - new ReentrantLock(true)  A fair lock will be initialized (FairSync). For fair locks , The thread with the longest wait time will get the lock first . If the lock is held by the read thread , Then another thread requests a write lock , Then no other read thread can get a read lock , Until the write thread releases the write lock .

ReentrantReadWriteLock Use case of

stay  ReentrantReadWriteLock  Characteristics of   Has been introduced in ,ReentrantReadWriteLock  Read and write lock of (ReadLockWriteLock) It's all done  Lock  Interface , So they are independent of each other  ReentrantLock  equally , No more details here .

ReentrantReadWriteLock  And  ReentrantLock  Differences in usage , It mainly depends on the use of read-write lock . This article uses a typical scenario to explain .

【 Example 】 be based on  ReadWriteLock  Implement a simple generic unbounded cache

/**
* Simple implementation of unbounded cache
* <p>
* Use WeakHashMap Store key value pairs .WeakHashMap Objects stored in are weak references ,JVM GC It will automatically clear the weak reference objects that are not referenced .
*/
static class UnboundedCache<K, V> {
private final Map<K, V> cacheMap = new WeakHashMap<>();
private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();
public V get(K key) {
cacheLock.readLock().lock();
V value;
try {
value = cacheMap.get(key);
String log = String.format("%s Reading data %s:%s", Thread.currentThread().getName(), key, value);
System.out.println(log);
} finally {
cacheLock.readLock().unlock();
}
return value;
}
public V put(K key, V value) {
cacheLock.writeLock().lock();
try {
cacheMap.put(key, value);
String log = String.format("%s Write data %s:%s", Thread.currentThread().getName(), key, value);
System.out.println(log);
} finally {
cacheLock.writeLock().unlock();
}
return value;
}
public V remove(K key) {
cacheLock.writeLock().lock();
try {
return cacheMap.remove(key);
} finally {
cacheLock.writeLock().unlock();
}
}
public void clear() {
cacheLock.writeLock().lock();
try {
this.cacheMap.clear();
} finally {
cacheLock.writeLock().unlock();
}
}
}

explain :

  • Use  WeakHashMap  instead of  HashMap  To store key value pairs .WeakHashMap  Objects stored in are weak references ,JVM GC It will automatically clear the weak reference objects that are not referenced .
  • towards  Map  Add write lock before writing data , After finish , Release the write lock .
  • towards  Map  Add read lock before reading data , After reading , Release read lock .

Test its thread security :

/**
* @author <a href="mailto:forbreak@163.com">Zhang Peng</a>
* @since 2020-01-01
*/
public class ReentrantReadWriteLockDemo {
static UnboundedCache<Integer, Integer> cache = new UnboundedCache<>();
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executorService.execute(new MyThread());
cache.get(0);
}
executorService.shutdown();
}
/** The thread task writes... To the cache every time 3 A random value ,key Fix */
static class MyThread implements Runnable {
@Override
public void run() {
Random random = new Random();
for (int i = 0; i < 3; i++) {
cache.put(i, random.nextInt(100));
}
}
}
}

explain : Example , Start from thread pool 20 Concurrent tasks . The task writes... To the cache every time 3 A random value ,key Fix ; Then the main thread reads the first one in the cache every time key Value .

Output results :

main Reading data 0:null
pool-1-thread-1 Write data 0:16
pool-1-thread-1 Write data 1:58
pool-1-thread-1 Write data 2:50
main Reading data 0:16
pool-1-thread-1 Write data 0:85
pool-1-thread-1 Write data 1:76
pool-1-thread-1 Write data 2:46
pool-1-thread-2 Write data 0:21
pool-1-thread-2 Write data 1:41
pool-1-thread-2 Write data 2:63
main Reading data 0:21
main Reading data 0:21
// ...

4.3. ReentrantReadWriteLock Principle

I've learned about  ReentrantLock  Principle , understand  ReentrantReadWriteLock  It's much easier .

ReentrantReadWriteLock Data structure of

read ReentrantReadWriteLock Source code , You can see that it has three core fields :

/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
  • sync - Inner class  ReentrantReadWriteLock.Sync  object . And  ReentrantLock  similar , It has two subclasses :ReentrantReadWriteLock.FairSync  and  ReentrantReadWriteLock.NonfairSync , It represents the realization of fair lock and unfair lock respectively .
  • readerLock - Inner class  ReentrantReadWriteLock.ReadLock  object , This is a reading lock .
  • writerLock - Inner class  ReentrantReadWriteLock.WriteLock  object , This is a writing lock .

ReentrantReadWriteLock Get lock and release lock

public static class ReadLock implements Lock, java.io.Serializable {
// call AQS Get shared lock method
public void lock() {
sync.acquireShared(1);
}
// call AQS Release shared lock method
public void unlock() {
sync.releaseShared(1);
}
}
public static class WriteLock implements Lock, java.io.Serializable {
// call AQS Get exclusive lock method
public void lock() {
sync.acquire(1);
}
// call AQS Release exclusive lock method
public void unlock() {
sync.release(1);
}
}

5. StampedLock

ReadWriteLock Two modes are supported : One is read lock , One is a write lock . and StampedLock Three modes are supported , Namely : Write lock 、 Pessimistic read lock and optimistic read . among , Write lock 、 The semantics of pessimistic read lock and ReadWriteLock Write lock of 、 The semantics of read locks are very similar , Allow multiple threads to acquire pessimistic read locks at the same time , But only one thread is allowed to get the write lock , Write lock and pessimistic read lock are mutually exclusive . The difference is :StampedLock After the write lock and pessimistic read lock are locked successfully , Will return to one stamp; And then when it's unlocked , Need to pass in this stamp.

Note that there , It's using “ Read optimistically ” The word , instead of “ Optimistic reading lock ”, To remind you of , Optimistic reading this operation is unlocked , So compared to ReadWriteLock Read lock , The performance of optimistic reading is better .

StampedLock The performance of is better than ReadWriteLock Still better , The key is  StampedLock The way to support optimistic reading .

  • ReadWriteLock Support multiple threads to read at the same time , But when multiple threads read at the same time , All writes will be blocked ;
  • and StampedLock Provide optimistic reading , It allows a thread to acquire a write lock , In other words, not all write operations are blocked .

For reading more and writing less StampedLock Performance is very good , Simple application scenarios can basically replace ReadWriteLock, however StampedLock Its function is just ReadWriteLock Subset , In use , There are still a few places to pay attention to .

  • StampedLock Reentry... Is not supported
  • StampedLock Read the lock of pessimism 、 Write locks do not support conditional variables .
  • If the thread is blocked in StampedLock Of readLock() perhaps writeLock() Upper time , At this point, call the blocking thread's interrupt() Method , It can lead to CPU soaring . Use StampedLock Do not call interrupt operation , If you need to support interrupt function , Be sure to use interruptible pessimistic read locks readLockInterruptibly() And write lock writeLockInterruptibly().

【 Example 】StampedLock Blocking time , call interrupt() Lead to CPU soaring

final StampedLock lock
= new StampedLock();
Thread T1 = new Thread(()->{
// Get write lock
lock.writeLock();
// It's stuck here forever , Do not release the write lock
LockSupport.park();
});
T1.start();
// Guarantee T1 Get write lock
Thread.sleep(100);
Thread T2 = new Thread(()->
// Blocking in pessimistic read lock
lock.readLock()
);
T2.start();
// Guarantee T2 Blocking in the read lock
Thread.sleep(100);
// Interrupt threads T2
// Will cause threads T2 Where CPU soaring
T2.interrupt();
T2.join();

【 Example 】StampedLock Read the template :

final StampedLock sl =
new StampedLock();
// Read optimistically
long stamp =
sl.tryOptimisticRead();
// Read in method local variables
......
// check stamp
if (!sl.validate(stamp)){
// Upgrade to pessimistic read lock
stamp = sl.readLock();
try {
// Read in method local variables
.....
} finally {
// Release pessimistic reading lock
sl.unlockRead(stamp);
}
}
// Use method local variables to perform business operations
......

【 Example 】StampedLock Write template :

long stamp = sl.writeLock();
try {
// Write shared variables
......
} finally {
sl.unlockWrite(stamp);
}

6. AQS

AbstractQueuedSynchronizer( abbreviation  AQS) Is the queue synchronizer , seeing the name of a thing one thinks of its function , Its main function is to deal with synchronization . It is the cornerstone of concurrent locks and many synchronization utility classes ( Such as  ReentrantLockReentrantReadWriteLockCountDownLatchSemaphoreFutureTask  etc. ).

6.1. AQS Main points

AQS Provides support for exclusive locks and shared locks .

stay  java.util.concurrent.locks  The related lock in the bag ( Commonly used  ReentrantLock、 ReadWriteLock) It's all based on AQS To achieve . None of these locks inherit directly AQS, It defines a  Sync  Class to inherit AQS. Why do you do this ? Because the lock is for the user , And synchronizer is oriented to thread control , Then aggregate synchronizers instead of direct inheritance in lock implementation AQS Can be a good separation of the two concerns .

6.2. AQS Application

AQS Provides support for exclusive locks and shared locks .

Exclusive lock API

obtain 、 The main thing about releasing the exclusive lock API as follows :

public final void acquire(int arg)
public final void acquireInterruptibly(int arg)
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
public final boolean release(int arg)
  • acquire - Acquire exclusive lock .
  • acquireInterruptibly - Get an exclusive lock that can be interrupted .
  • tryAcquireNanos - Try to get an interruptible exclusive lock within a specified time . Return to... In three cases :
    • Within the timeout period , The current thread successfully acquired the lock ;
    • The current thread is interrupted during the timeout ;
    • End of timeout , Still not get lock back false.
  • release - Release exclusive lock .

Shared lock API

obtain 、 The main thing about releasing shared locks API as follows :

public final void acquireShared(int arg)
public final void acquireSharedInterruptibly(int arg)
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
public final boolean releaseShared(int arg)
  • acquireShared - Get shared lock .
  • acquireSharedInterruptibly - Get an interruptible shared lock .
  • tryAcquireSharedNanos - Try to obtain an interruptible shared lock within a specified time .
  • release - Release the shared lock .

6.3. AQS Principle

ASQ Principle points :

  • AQS Use an integer  volatile  Variable to   Maintain synchronization status . The meaning of a state is given by subclasses .
  • AQS One was maintained FIFO Double linked list of , Thread used to store failed to get lock .

AQS Provides two basic operations around the synchronization state “ obtain ” and “ Release ”, And provide a series of judgment and treatment methods , A few simple points :

  • state It's exclusive , It's still shared ;
  • state After being captured , Other threads need to wait ;
  • state After being released , Wake up waiting thread ;
  • The thread is not in time , How to quit waiting .

As to whether the thread can get state, How to release state, It's not AQS Concerned about , To be implemented by subclasses .

AQS Data structure of

read AQS Source code , You can find :AQS Inherited from  AbstractOwnableSynchronize.

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Waiting for the head of the line , Lazy loading . Only through setHead Methods to modify . */
private transient volatile Node head;
/** Wait for the end of the line , Lazy loading . Only through enq Method to add a new waiting node .*/
private transient volatile Node tail;
/** sync */
private volatile int state;
}
  • state - AQS Use an integer  volatile  Variable to   Maintain synchronization status .
    • The meaning of this integer state is given by subclasses , Such as ReentrantLock  The state value indicates the number of times the owner thread has repeatedly acquired the lock ,Semaphore  The status value in represents the remaining number of licenses .
  • head  and  tail - AQS  One was maintained  Node  type (AQS The inner class of ) Double linked list to complete the synchronous state management . This double linked list is a two-way FIFO queue , adopt  head  and  tail  Pointer to access . When   After a thread fails to acquire the lock , Is added to the end of the queue .

img

Let's take a look at  Node  Source code

static final class Node {
/** The node waiting for synchronization is in shared mode */
static final Node SHARED = new Node();
/** The node waiting for synchronization is in exclusive mode */
static final Node EXCLUSIVE = null;
/** Thread wait state , The state values are : 0、1、-1、-2、-3 */
volatile int waitStatus;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
/** Precursor node */
volatile Node prev;
/** The subsequent nodes */
volatile Node next;
/** Thread waiting for lock */
volatile Thread thread;
/** It's related to whether the nodes share */
Node nextWaiter;
}

Obviously ,Node It's a double linked list structure .

  • waitStatus - Node  Use an integer  volatile  Variable to maintain AQS Synchronize the status of thread nodes in the queue .waitStatus  There are five states :
    • CANCELLED(1) - This state indicates : The thread of this node may be timeout or interrupted   Being cancelled ( To void ) state , Once in this state , Indicates that this node should be removed from the waiting queue .
    • SIGNAL(-1) - This state indicates : Subsequent nodes will be suspended , So after the current node releases the lock or is cancelled , Must wake up (unparking) Its successor node .
    • CONDITION(-2) - This state indicates : The thread of this node   In a waiting condition , It will not be treated as a node on the synchronization queue , Until awakened (signal), Set the value to 0, And then re-enter the blocking state .
    • PROPAGATE(-3) - This state indicates : next  acquireShared  It should spread unconditionally .
    • 0 - Not above .

Acquisition and release of exclusive lock

Acquire exclusive lock

AQS Use in  acquire(int arg)  Method get exclusive lock , The general process is as follows :

  1. Try to get synchronization status first , If the synchronization status is obtained successfully , Then the end method , Go straight back to .
  2. If getting synchronization status is not successful ,AQS Will keep trying to make use of CAS Operation inserts the current thread into the end of the queue waiting for synchronization , Until we succeed .
  3. next , Keep trying to acquire exclusive lock for thread node in waiting queue .

img

img

The detailed process can be shown in the figure below , Please combine the source code to understand ( A picture is worth a thousand words ):

img

Release exclusive lock

AQS Use in  release(int arg)  Method to release the exclusive lock , The general process is as follows :

  1. First try to get the synchronization status of the unlocked thread , If getting synchronization status is not successful , Then the end method , Go straight back to .
  2. If the synchronization status is obtained successfully ,AQS Will try to wake up the successor node of the current thread node .
Get an exclusive lock that can be interrupted

AQS Use in  acquireInterruptibly(int arg)  Method to obtain an interruptible exclusive lock .

acquireInterruptibly(int arg)  Compared with the method of acquiring exclusive lock ( acquire) Very similar , The only difference is that it will pass through  Thread.interrupted  Detect whether the current thread is interrupted , If it is , Throw an interrupt exception immediately (InterruptedException).

Get the exclusive lock of timeout wait type

AQS Use in  tryAcquireNanos(int arg)  Method to obtain an exclusive lock for a timeout wait .

doAcquireNanos How to implement   Compared to acquiring exclusive lock method ( acquire) Very similar , The difference is that it calculates the deadline based on the timeout and the current time . In the process of acquiring lock , Will continue to determine whether the timeout , If the timeout , Go straight back to false; If it doesn't time out , Then use  LockSupport.parkNanos  To block the current thread .

Acquisition and release of shared lock

Get shared lock

AQS Use in  acquireShared(int arg)  Method to obtain the shared lock .

acquireShared  Methods and  acquire  The logic of the method is very similar , The difference only lies in the spin conditions and the operation of node queuing .

The conditions for successfully obtaining the shared lock are as follows :

  • tryAcquireShared(arg)  The return value is greater than or equal to 0 ( This means sharing locks permit Not used up yet ).
  • The predecessor of the current node is the head node .
Release the shared lock

AQS Use in  releaseShared(int arg)  Method to release the shared lock .

releaseShared  First try to release the synchronization state , If it works , Then unlock one or more successor thread nodes . The process of releasing shared lock is similar to that of releasing exclusive lock , The difference lies in :

For exclusive mode , if necessary SIGNAL, Release is only equivalent to calling the header node's  unparkSuccessor.

Get an interruptible shared lock

AQS Use in  acquireSharedInterruptibly(int arg)  Method to obtain an interruptible shared lock .

acquireSharedInterruptibly  Methods and  acquireInterruptibly  Almost unanimously , I won't repeat .

Get a timeout wait shared lock

AQS Use in  tryAcquireSharedNanos(int arg)  Method to obtain a timeout waiting shared lock .

tryAcquireSharedNanos  Methods and  tryAcquireNanos  Almost unanimously , I won't repeat .

7. Deadlock

7.1. What is a deadlock

Deadlock is a specific program state , Between entities , Due to circular dependence, each other has been waiting for each other , No one can move on . Deadlock is not just between threads , There is also a process where resources are exclusive There may be a deadlock . Generally speaking , Most of us focus on deadlocks in multithreading scenarios , Between two or more threads , Because each other holds the locks each other needs , And it's permanently blocked .

7.2. How to locate a deadlock

The most common way to locate deadlocks is to use jstack Wait for tools to get thread stack , And then position the interdependencies , And then find the deadlock . If it's a more obvious deadlock , Often jstack And then we can directly locate , similar JConsole Even limited deadlock detection can be done in the graphical interface .

If we are developing our own management tools , You need to scan the service process in a more procedural way 、 Positioning deadlocks , Consider using Java Standard management provided API,ThreadMXBean, It directly provides  findDeadlockedThreads()  Method is used to locate .

7.3. How to avoid deadlock

Basically, deadlock happens because :

  • Mutually exclusive , similar Java in Monitor It's all exclusive .
  • Keep mutually exclusive for a long time , Before the end of use , Don't release , It can't be preempted by other threads .
  • Cyclic dependence , There is a cyclic dependence of locks between multiple individuals , Depend on each other to release the lock .

thus , We can analyze the ideas and methods to avoid deadlock .

(1) Avoid one thread acquiring multiple locks at the same time .

Avoid a thread taking up multiple resources in the lock at the same time , Try to make sure that each lock occupies only one resource .

Try using a time lock  lock.tryLock(timeout), Avoid the lock never being released .

For database locks , Locking and unlocking must be in a database connection , Otherwise, the unlocking will fail .

There is a complete Java primary , Advanced corresponding learning routes and materials ! Focus on java Development . Share java Basics 、 Knowledge of principle 、JavaWeb actual combat 、spring Family bucket 、 Design patterns 、 Distributed and interview materials 、 Open source project , Help developers grow !


Welcome to WeChat official account. : Code state Lord

版权声明
本文为[Code state Lord]所创,转载请带上原文链接,感谢

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云