Zheng Shuang can also understand the principle of zookeeper distributed lock

Java bag 2021-01-22 15:49:16
zheng shuang understand principle zookeeper


Introduce

In many scenarios , Data consistency is an important topic , In a stand-alone environment , We can go through Java Concurrency provided API To solve ; And in a distributed environment ( There will be network failures 、 Duplicate message 、 Information loss and other issues ) It's much more complicated , For example, e-commerce's inventory deduction , Seckill activity , Cluster timing, task execution and other scenarios that require mutual exclusion of processes . This paper mainly discusses how to use Zookeeper To implement distributed locking , The advantages and disadvantages of some other distributed lock schemes are compared . As for what to use , It depends on your business scenario , There is no absolute solution .

What are distributed locks ?

Distributed lock is a way to control the synchronous access of shared resources between distributed systems .

Implementation of distributed lock attention :
  • The reentry of locks ( Recursive calls should not be blocked 、 Avoid deadlock )
  • Lock timeout ( Avoid deadlock 、 Dead loop and other unexpected situations )
  • Lock block ( Guarantee atomicity, etc )
  • Lock features support ( Blocking lock 、 Reentrant lock 、 Fair lock 、 interlocking 、 Semaphore 、 Read-write lock )
Pay attention to using distributed locks :
  • The cost of distributed locks ( Distributed locks are usually not used , Some scenarios can be replaced by optimistic locks )
  • Lock granularity ( Control the granularity of locking , Can optimize the performance of the system )
  • The way to lock

Common implementation of distributed lock scheme

database

Based on database table unique index

The simplest way is to create a lock table directly , When we want to lock a method or resource , Let's add a record to the table , Delete this record when you want to release the lock . Add a uniqueness constraint to a field , If there are multiple requests submitted to the database at the same time , The database will guarantee that only one operation can succeed , Then we can think that the successful thread obtained the lock of the method , Method body content... Can be executed .

But it will introduce a single point of database 、 No time to failure 、 Don't block 、 Non reentrant and so on .

Exclusive lock based on Database

If you are using MySql Of InnoDB engine , Add after query statement for update, The database will be in the process of query ( You have to query through a unique index ) Add exclusive locks to database tables , We can think that the thread that obtains the exclusive lock can obtain the distributed lock , adopt connection.commit() Operation to release the lock .

Will introduce database single point 、 Do not reenter 、 There is no guarantee that row locks will be used 、 Exclusive lock , So it's possible to not submit for a long time, leading to problems such as occupying database connection .

Advantages and disadvantages

  • advantage :

Directly with the help of database , Easy to understand .

  • shortcoming

It will introduce more problems , Make the whole project more and more complicated

Operating a database requires a certain amount of overhead , There are some performance problems

Using row level locks in a database is not necessarily reliable , Especially when our lock watch is not big

cache

Compared with the distributed lock scheme based on Database , Cache based implementations will perform better in terms of performance , There are many mature caching products at present , Include Redis、memcached、tair etc. .

be based on redis Of setnx()、expire() Method to do distributed lock

setnx That means SET if Not Exists, There are two main parameters setnx(key, value). This method is atomic , If key non-existent , Set current key success , return 1; If at present key Already exist , Set current key Failure , return 0.

expire Set expiration time , It should be noted that setnx Command cannot be set key Timeout for , Only through expire() Come on key Set up .

redis There's a command to do setnx and expire Atomic instructions with the same effect , The explanation of the blogger did not mention . command :
set k1 v1 ex 10 nx . Of course, in order to realize the above two commands atomic operation , You can also use lua Script completion .

be based on Redlock Do distributed locks

Redlock yes Redis The author of antirez This paper gives the model of cluster Redis Distributed lock , It's based on N Completely independent Redis node ( Usually N It can be set to 5)

be based on redisson Do distributed locks

redisson yes redis The official distributed lock component

Advantages and disadvantages

  • advantage

Good performance

  • shortcoming

There are too many factors to consider in the implementation ,
It's not very reliable to control the lock's failure time by timeout

Zookeeper

General idea

When each client locks a method , stay Zookeeper In the specified node directory corresponding to this method , Generate a unique temporary ordered node (zk It has the function of automatically generating ordered nodes ). The way to determine whether to acquire a lock is simple , Only one of the ordered nodes with the smallest sequence number needs to be judged . When the lock is released , Just delete the temporary node . meanwhile , It can avoid the lock cannot be released due to service downtime , And the deadlock problem

Zookeeper The core principle of implementing distributed lock

1. Exclusive lock

Exclusive lock , Also known as write lock or exclusive lock . If the transaction T1 For data objects O1 With an exclusive lock , So during the whole locking period , Only transactions are allowed T1 Yes O1 To perform a read or update operation , No other transaction can operate on this data object , until T1 Release the lock .

The core of exclusive lock is to ensure that only one transaction obtains the lock , And after the lock is released , All transactions that are waiting to acquire a lock can be notified of .

Zookeeper Strong consistency property of , It can guarantee the high concurrency of distributed system , Creating nodes ensures global uniqueness , You can use Zookeeper This feature , Achieve exclusive lock .

Reading and writing are mutually exclusive 、 Writing mutually exclusive 、 Reading is mutually exclusive

Realization principle , Yes 3 Core steps : Definite lock 、 Get the lock 、 Release the lock

  • Definite lock

adopt Zookeeper To represent a lock

  • Get the lock

The client calls create Method to create a temporary node that represents a lock . Create success , Think that the client gets the lock . Create failure , Think the lock is occupied . At the same time, let the node that has not obtained the lock register on the node Watcher monitor , In order to monitor and hear in real time lock Changes of nodes , Get the lock again .

  • Release the lock

The client currently obtaining the lock is down or abnormal , that Zookeeper The temporary node will be deleted , The lock was released .

Normal execution of business logic , The client takes the initiative to delete the temporary node created by itself .

2. Shared lock

Shared lock , Also called read lock . If the transaction T1 For data objects O1 Added shared lock , The current transaction can only deal with O1 Read operation , Other transactions can only apply a shared lock to this data object , Until all shared locks on the data object are released .

The difference between shared locks and exclusive locks is that , After adding the exclusive lock , Data objects are only visible to the current transaction , And with shared locks , Data objects are visible to all transactions .

summary , Read read share , Reading and writing are mutually exclusive . All read requests do not lock resources , Resource sharing . Lock resources when you read and write .

The realization principle is also 3 Core steps :

  • Definite lock

adopt Zookeeper To represent a lock , Is a similar to /lockpath/[hostname]- Request type - Serial number Temporary order node for

  • Get the lock

The client calls create Method to create a temporary order node that represents a lock . If it's a read request , Create /lockpath/[hostname]-R- Serial number node , If it is a write request, create /lockpath/[hostname]-W- Serial number node

The logic to obtain the shared lock is :

  1. After creating the node , obtain /lockpath All children under node , And register the change of child node to this node Watcher monitor
  2. Determine your own node number
  3. For read requests , If you find that there is a write request smaller than your own serial number, wait to continue to acquire the lock , If not, get the shared lock , Operating resources . For write requests , If you find a reading that is smaller than your serial number / Write the request and wait to get the lock , If not, get the lock .
  4. Received Watcher Upon receipt of the notice , Repeat step 1
  • Release the lock

Consistent with exclusive lock logic .

3. The generation of herding

In the implementation of shared lock “ Judge the order of reading and writing ” Of the 1 One step is : After creating the node , obtain /lockpath All children under node , And register the change of child node to this node Watcher monitor . In this case , Any time the client removes the shared lock ,Zookeeper Will send child node changed Watcher Inform all machines , There will be a lot of “Watcher notice ” and “ Get the list of child nodes ” This operation is repeated , Then all nodes judge whether they are the node with the smallest sequence number ( Write requests ) Or judge whether the child nodes smaller than their own serial numbers are all read requests ( Read request ), To continue waiting for the next notification .

However , Many of these repetitions are “ Useless ”, In fact, each lock competitor only needs to pay attention to the existence of the node whose serial number is smaller than itself .

When the cluster size is relatively large , these “ Useless ” It's not just about Zookeeper It has a huge performance impact and network impact , What's more serious is , If more than one client releases the shared lock at the same time ,Zookeeper The server will send a large number of event notifications to other clients in a short time – That's what's called “ Herd behaviour “.

Improved distributed lock implementation :

  1. Client calls create Method to create a similar to /lockpath/[hostname]- Request type - Serial number Temporary order node for
  2. Client calls getChildren Method to get a list of all the child nodes that have been created ( We don't register anything here Watcher)
  3. Read request : Register with the last write request node smaller than its own serial number Watcher monitor , Write requests : Register with the last node whose serial number is smaller than your own Watcher monitor ( The logic of whether there is a lock is the same as above )
  4. wait for Watcher monitor , Continue to step 2

use Curator The client implements distributed locks

Apache Curator It's a Zookeeper Open source client for , It provides Zookeeper Various application scenarios (Recipe, Such as shared lock service 、master The election 、 Distributed counter, etc ) The abstract encapsulation of , Next, we'll use Curator Provides classes to implement distributed locks ,Curator The provided classes related to distributed locks are 5 individual , Namely :

  1. Shared Reentrant Lock Reentrant lock
  2. Shared Lock Shared non reentrant locks
  3. Shared Reentrant Read Write Lock Reentrant read-write lock
  4. Shared Semaphore Semaphore
  5. Multi Shared Lock Multilock
About error handling : It is highly recommended to use ConnectionStateListener Dealing with changes in connection state . When the connection LOST You no longer have a lock .
1. Reentrant lock

Shared Reentrant Lock, Globally reentrant locks , All clients can request , The same client owns the lock at the same time , Can be obtained multiple times , Not blocked . It is made up of classes InterProcessMutex To achieve , Its main method :

// Construction method
public InterProcessMutex(CuratorFramework client, String path)
public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver)
// adopt acquire Gets the lock , And provide a timeout mechanism :
public void acquire() throws Exception
public boolean acquire(long time, TimeUnit unit) throws Exception
// Unlock the lock
public void makeRevocable(RevocationListener<InterProcessMutex> listener)
public void makeRevocable(final RevocationListener<InterProcessMutex> listener, Executor executor)

Define a FakeLimitedResource Class to simulate shared resources , This resource can only be used by one thread at a time , Until the end of use , The next thread can use , Otherwise, an exception will be thrown .

public class FakeLimitedResource {
private final AtomicBoolean inUse = new AtomicBoolean(false);
// Simulate resources that can only be operated by a single thread
public void use() throws InterruptedException {
if (!inUse.compareAndSet(false, true)) {
// In the case of proper use of locks , This exception cannot be thrown
throw new IllegalStateException("Needs to be used by one client at a time");
}
try {
Thread.sleep((long) (100 * Math.random()));
} finally {
inUse.set(false);
}
}
}

The following code will create N Threads to simulate nodes in a distributed system , The system will go through InterProcessMutex To control the synchronous use of resources .

Each node will initiate 10 Requests , complete Request lock -- Access resources -- Ask for the lock again -- Release the lock -- Release the lock The process of .

Client pass acquire Request lock , adopt release Release the lock , Get a few locks and release them .

This shared resource can only be used by one thread at a time , If control synchronization fails , Will throw exceptions .

public class SharedReentrantLockTest {
private static final String lockPath = "/testZK/sharedreentrantlock";
private static final Integer clientNums = 5;
final static FakeLimitedResource resource = new FakeLimitedResource(); // Shared resources
private static CountDownLatch countDownLatch = new CountDownLatch(clientNums);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < clientNums; i++) {
String clientName = "client#" + i;
new Thread(new Runnable() {
@Override
public void run() {
CuratorFramework client = ZKUtils.getClient();
client.start();
Random random = new Random();
try {
final InterProcessMutex lock = new InterProcessMutex(client, lockPath);
// Each client requests 10 Sharing resources
for (int j = 0; j < 10; j++) {
if (!lock.acquire(10, TimeUnit.SECONDS)) {
throw new IllegalStateException(j + ". " + clientName + " Can't get mutex ");
}
try {
System.out.println(j + ". " + clientName + " Mutex has been acquired ");
resource.use(); // Use resources
if (!lock.acquire(10, TimeUnit.SECONDS)) {
throw new IllegalStateException(j + ". " + clientName + " Can't get mutex again ");
}
System.out.println(j + ". " + clientName + " Mutex has been acquired again ");
lock.release(); // Apply the lock several times, release the lock several times
} finally {
System.out.println(j + ". " + clientName + " Release the mutex ");
lock.release(); // Always in finally Middle release
}
Thread.sleep(random.nextInt(100));
}
} catch (Throwable e) {
System.out.println(e.getMessage());
} finally {
CloseableUtils.closeQuietly(client);
System.out.println(clientName + " Client shutdown !");
countDownLatch.countDown();
}
}
}).start();
}
countDownLatch.await();
System.out.println(" end !");
}
}

Console print log , You can see that synchronous access control to the resource is successful , And the lock is reentrant

0. client#3 Mutex has been acquired
0. client#3 Mutex has been acquired again
0. client#3 Release the mutex
0. client#1 Mutex has been acquired
0. client#1 Mutex has been acquired again
0. client#1 Release the mutex
0. client#2 Mutex has been acquired
0. client#2 Mutex has been acquired again
0. client#2 Release the mutex
0. client#0 Mutex has been acquired
0. client#0 Mutex has been acquired again
0. client#0 Release the mutex
0. client#4 Mutex has been acquired
0. client#4 Mutex has been acquired again
0. client#4 Release the mutex
1. client#1 Mutex has been acquired
1. client#1 Mutex has been acquired again
1. client#1 Release the mutex
2. client#1 Mutex has been acquired
2. client#1 Mutex has been acquired again
2. client#1 Release the mutex
1. client#4 Mutex has been acquired
1. client#4 Mutex has been acquired again
1. client#4 Release the mutex
1. client#3 Mutex has been acquired
1. client#3 Mutex has been acquired again
1. client#3 Release the mutex
1. client#2 Mutex has been acquired
1. client#2 Mutex has been acquired again
1. client#2 Release the mutex
2. client#4 Mutex has been acquired
2. client#4 Mutex has been acquired again
2. client#4 Release the mutex
....
....
client#2 Client shutdown !
9. client#0 Mutex has been acquired
9. client#0 Mutex has been acquired again
9. client#0 Release the mutex
9. client#3 Mutex has been acquired
9. client#3 Mutex has been acquired again
9. client#3 Release the mutex
client#0 Client shutdown !
8. client#4 Mutex has been acquired
8. client#4 Mutex has been acquired again
8. client#4 Release the mutex
9. client#4 Mutex has been acquired
9. client#4 Mutex has been acquired again
9. client#4 Release the mutex
client#3 Client shutdown !
client#4 Client shutdown !
end !

At the same time, check during the program run Zookeeper Node tree , It can be found that each lock request actually corresponds to a temporary order node

[zk: localhost:2181(CONNECTED) 42] ls /testZK/sharedreentrantlock
[leases, _c_208d461b-716d-43ea-ac94-1d2be1206db3-lock-0000001659, locks, _c_64b19dba-3efa-46a6-9344-19a52e9e424f-lock-0000001658, _c_cee02916-d7d5-4186-8867-f921210b8815-lock-0000001657]
2. Do not reenter the lock

Shared Lock And Shared Reentrant Lock be similar , But don't re-enter , This non reentrant lock is created by the class InterProcessSemaphoreMutex To achieve , The usage is similar to the above .

Put... In the above program InterProcessMutex Replace it with a non reentrant lock InterProcessSemaphoreMutex, If you run the above code again , The result is that the thread is blocked in the second acquire On , Until timeout , That is, the lock is not reentrant . The console output log is as follows :

0. client#2 Mutex has been acquired
0. client#1 Can't get mutex
0. client#4 Can't get mutex
0. client#0 Can't get mutex
0. client#3 Can't get mutex
client#1 Client shutdown !
client#4 Client shutdown !
client#3 Client shutdown !
client#0 Client shutdown !
0. client#2 Release the mutex
0. client#2 Can't get mutex again
client#2 Client shutdown !
end !

Comment on the second code to get the lock , The program can be executed normally

0. client#1 Mutex has been acquired
0. client#1 Release the mutex
0. client#2 Mutex has been acquired
0. client#2 Release the mutex
0. client#0 Mutex has been acquired
0. client#0 Release the mutex
0. client#4 Mutex has been acquired
0. client#4 Release the mutex
0. client#3 Mutex has been acquired
0. client#3 Release the mutex
1. client#1 Mutex has been acquired
1. client#1 Release the mutex
1. client#2 Mutex has been acquired
1. client#2 Release the mutex
....
....
9. client#4 Mutex has been acquired
9. client#4 Release the mutex
9. client#0 Mutex has been acquired
client#2 Client shutdown !
9. client#0 Release the mutex
9. client#1 Mutex has been acquired
client#0 Client shutdown !
client#4 Client shutdown !
9. client#1 Release the mutex
9. client#3 Mutex has been acquired
client#1 Client shutdown !
9. client#3 Release the mutex
client#3 Client shutdown !
end !
3. Reentrant read-write lock

Shared Reentrant Read Write Lock, Reentrant read-write lock , A read-write lock manages a pair of related locks , One is responsible for reading operations , The other one is responsible for writing operations ; Read operations can be used by multiple processes when the write lock is not in use , The write lock is not allowed to read when it is in use ( Blocking ); This lock is reentrant ; A thread with a write lock can re-enter a read lock , But the read lock cannot enter the write lock , This also means that write locks can be degraded to read locks , such as Request write lock ---> Read the lock ----> Release the write lock ; Upgrading from a read lock to a write lock is not possible .

Reentrant read-write locks are mainly implemented by two classes :InterProcessReadWriteLock、InterProcessMutex, When you use it, first create a InterProcessReadWriteLock example , And then according to your needs to get read lock or write lock , The type of read-write lock is InterProcessMutex.

It can be understood as the shared lock we analyzed above
 public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < clientNums; i++) {
final String clientName = "client#" + i;
new Thread(new Runnable() {
@Override
public void run() {
CuratorFramework client = ZKUtils.getClient();
client.start();
final InterProcessReadWriteLock lock = new InterProcessReadWriteLock(client, lockPath);
final InterProcessMutex readLock = lock.readLock();
final InterProcessMutex writeLock = lock.writeLock();
try {
// Note that you can only get a write lock and then a read lock , It can't be reversed !!!
if (!writeLock.acquire(10, TimeUnit.SECONDS)) {
throw new IllegalStateException(clientName + " Unable to get a write lock ");
}
System.out.println(clientName + " A write lock has been obtained ");
if (!readLock.acquire(10, TimeUnit.SECONDS)) {
throw new IllegalStateException(clientName + " Can't get read lock ");
}
System.out.println(clientName + " Read lock obtained ");
try {
resource.use(); // Use resources
} finally {
System.out.println(clientName + " Release the read-write lock ");
readLock.release();
writeLock.release();
}
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
CloseableUtils.closeQuietly(client);
countDownLatch.countDown();
}
}
}).start();
}
countDownLatch.await();
System.out.println(" end !");
}
}

Console print log

client#1 A write lock has been obtained
client#1 Read lock obtained
client#1 Release the read-write lock
client#2 A write lock has been obtained
client#2 Read lock obtained
client#2 Release the read-write lock
client#0 A write lock has been obtained
client#0 Read lock obtained
client#0 Release the read-write lock
client#4 A write lock has been obtained
client#4 Read lock obtained
client#4 Release the read-write lock
client#3 A write lock has been obtained
client#3 Read lock obtained
client#3 Release the read-write lock
end !
4. Semaphore

Shared Semaphore, The semaphore of a count is similar to JDK Of Semaphore,JDK in Semaphore Set of licenses for maintenance (permits), and Cubator It's called a lease (Lease).

There are two ways to decide semaphore Maximum number of leases for , This is done when the constructor initializes . The first way is given by the user maxLeases decision , The second way to use SharedCountReader class .

 public InterProcessSemaphoreV2(CuratorFramework client, String path, int maxLeases)
public InterProcessSemaphoreV2(CuratorFramework client, String path, SharedCountReader count)

Semaphore main implementation classes are

InterProcessSemaphoreV2 - Semaphore implementation class
Lease - lease ( Single signal )
SharedCountReader - Counter , Used to calculate the maximum number of leases 

You can ask for multiple leases , By calling acquire Method to complete . If Semaphore The current lease is not enough , The request thread will be blocked , At the same time, it also provides overload method for timeout .

public Lease acquire() throws Exception
public Collection<Lease> acquire(int qty) throws Exception
public Lease acquire(long time, TimeUnit unit) throws Exception
public Collection<Lease> acquire(int qty, long time, TimeUnit unit) throws Exception

call acquire Will return a lease object , The client must be in finally in close These lease objects , Otherwise these leases will be lost . however , If the client session For some reason, such as crash lose , Then the leases held by these clients will automatically close, So that other clients can continue to use these leases . The lease can also be returned in the following ways , I can return one , You can also return multiple .

public void returnLease(Lease lease)
public void returnAll(Collection<Lease> leases) 

One Demo The procedure is as follows

public class SharedSemaphoreTest {
private static final int MAX_LEASE = 10;
private static final String PATH = "/testZK/semaphore";
private static final FakeLimitedResource resource = new FakeLimitedResource();
public static void main(String[] args) throws Exception {
CuratorFramework client = ZKUtils.getClient();
client.start();
InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, PATH, MAX_LEASE);
Collection<Lease> leases = semaphore.acquire(5);
System.out.println(" Get lease number :" + leases.size());
Lease lease = semaphore.acquire();
System.out.println(" Get a single lease ");
resource.use(); // Use resources
// Apply again to get 5 individual leases, here leases The quantity is only 4 individual , Not enough , Will time out
Collection<Lease> leases2 = semaphore.acquire(5, 10, TimeUnit.SECONDS);
System.out.println(" Get the lease , If the timeout will be null:" + leases2);
System.out.println(" Release the lease ");
semaphore.returnLease(lease);
// Apply again to get 5 individual , This is just enough
leases2 = semaphore.acquire(5, 10, TimeUnit.SECONDS);
System.out.println(" Get the lease , If the timeout will be null:" + leases2);
System.out.println(" Release all leases in the collection ");
semaphore.returnAll(leases);
semaphore.returnAll(leases2);
client.close();
System.out.println(" end !");
}
}

Console print log

 Get lease number :5
Get a single lease
Get the lease , If the timeout will be null:null
Release the lease
Get the lease , If the timeout will be null:[org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@3108bc, org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@370736d9, org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@5f9d02cb, org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@63753b6d, org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@6b09bb57]
Release all leases in the collection
end !
As mentioned above 4 All kinds of locks are fair locks (fair). from ZooKeeper From the perspective of , Each client gets the lock in the order of the request , Pretty fair .
5. Multilock

Multi Shared Lock It's a container for locks . When calling acquire, All locks will be locked acquire, If the request fails , All locks will be locked release. Also called release All locks are locked release( Failure ignored ). Basically , It is the representative of group lock , The request release operation above it is passed to all the locks it contains .

There are two main classes involved :

InterProcessMultiLock - Implement the class for the object
InterProcessLock - Distributed lock interface class 

Its constructor needs to contain a collection of locks , Or a group ZooKeeper Of path, Usage and Shared Lock phase

public InterProcessMultiLock(CuratorFramework client, List<String> paths)
public InterProcessMultiLock(List<InterProcessLock> locks)

One Demo The procedure is as follows

public class MultiSharedLockTest {
private static final String lockPath1 = "/testZK/MSLock1";
private static final String lockPath2 = "/testZK/MSLock2";
private static final FakeLimitedResource resource = new FakeLimitedResource();
public static void main(String[] args) throws Exception {
CuratorFramework client = ZKUtils.getClient();
client.start();
InterProcessLock lock1 = new InterProcessMutex(client, lockPath1); // Reentrant lock
InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, lockPath2); // Do not reenter the lock
// Group lock , Multilock
InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2));
if (!lock.acquire(10, TimeUnit.SECONDS)) {
throw new IllegalStateException(" Multiple locks cannot be acquired ");
}
System.out.println(" Multiple locks have been acquired ");
System.out.println(" Is there a first lock : " + lock1.isAcquiredInThisProcess());
System.out.println(" Is there a second lock : " + lock2.isAcquiredInThisProcess());
try {
resource.use(); // Resource operations
} finally {
System.out.println(" Release multiple locks ");
lock.release(); // Release multiple locks
}
System.out.println(" Is there a first lock : " + lock1.isAcquiredInThisProcess());
System.out.println(" Is there a second lock : " + lock2.isAcquiredInThisProcess());
client.close();
System.out.println(" end !");
}
}
There is no doubt after reading ? Let's leave a message to discuss , discuss !! Organize from the Internet , Unknown source , The article is slightly changed . In case of infringement, please contact us in time !
版权声明
本文为[Java bag]所创,转载请带上原文链接,感谢
https://javamana.com/2021/01/20210122154416198t.html

  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课程百度云