I heard that wechat search “Java fish” will change strong oh!
This article is in Java Server, which contains my complete series of Java articles, can be read for study or interview
(1) Introduction
In a monomer environment, Synchronized or RetreenLock is used to lock critical resources before they are called. However, in a distributed environment, locking individual resources does not work, so distributed locking is needed. The principle of distributed lock is to borrow an external system to act as a lock, such as Mysql, Redis, Zookeeper, etc. In the actual business, Redis and Zookeeper are used the most.
(2) Principle of Zookeeper lock
There are two types of locks: shared lock (read lock) and exclusive lock (write lock) Read lock: After one thread acquires the read lock, other threads can also acquire the read lock. However, other threads cannot acquire the write lock until the read lock is fully released. Write lock: When one thread acquires the write lock, other threads cannot acquire the read lock and write lock.
Zookeeper has a node type called temporary ordinal nodes, which can be used as a distributed lock implementation tool.
1. Create a node with a temporary serial number based on the id of the resource. /lock/mylockR0000000005 Read 2. Obtain all child nodes under /lock and check whether all nodes smaller than /lock are Read locks. If so, the lock is successfully obtained. 4. If a node is changed, perform step 2 again.
Create a node with a temporary serial number based on the id of the resource. /lock/mylockW0000000006 Write 2. Obtain all child nodes under /lock and check whether the smallest node is the parent node. If yes, the lock is successful.
A graph shows the phenomenon more clearly: first, the write lock is blocked because the write lock is not the first node, and 008 read lock is blocked because not all of the front nodes are read locks
Release the lock: Delete the corresponding temporary node. If the server goes down, deadlock will not occur because of the principle of temporary node.
(iii) Code implementation
In a real world scenario, locks would normally not be read for efficiency. Think about how inefficient it would be if someone was looking at the data and you couldn’t modify it. Here with code to achieve distributed write lock, first define a lock class
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Lock {
private String lockId;
private String path;
private boolean active;
public Lock(String lockId, String nodePath) {
this.lockId=lockId;
this.path=nodePath; }}Copy the code
Zookeeper then write a lock tool class, code has been annotated, the implementation principle and the above mentioned write lock acquisition principle:
public class ZookeeperLock {
private String server="192.168.78.128:2181";
private ZkClient zkClient;
private static final String rootPath="/lock";
// Initialize ZkClient and create the root node
public ZookeeperLock(a){
zkClient=new ZkClient(server,5000.20000);
buildRoot();
}
// Create root node
public void buildRoot(a){
// If the root node does not exist, create it
if(! zkClient.exists(rootPath)){ zkClient.createPersistent(rootPath); System.out.println("Root node created successfully"); }}public Lock lock(String lockId,long timeout){
// Create a temporary node
Lock lockNode=createLockNode(lockId);
// Try to deactivate the lock
lockNode=tryActiveLock(lockNode);
// If not activated, wait for timeout
if(! lockNode.isActive()){try {
synchronized(lockNode){ lockNode.wait(timeout); }}catch(InterruptedException e) { e.printStackTrace(); }}//timeout Indicates a lock timeout error if the node is not released during the timeout period
if(! lockNode.isActive()){throw new RuntimeException("lock timeout");
}
return lockNode;
}
/ / releases the lock
public void unlock(Lock lock){
if(lock.isActive()){ zkClient.delete(lock.getPath()); }}// Try activating the lock
private Lock tryActiveLock(Lock lockNode){
// Get all the child nodes
List<String> childList = zkClient.getChildren(rootPath)
.stream()
.sorted()
.map(p -> rootPath + "/" + p)
.collect(Collectors.toList());
// Get the first element
String firstNodePath = childList.get(0);
// Activate the lock if you are the first node
if (firstNodePath.equals(lockNode.getPath())){
lockNode.setActive(true);
}else {
// Otherwise listen on the previous lock
String upNodePath = childList.get(childList.indexOf(lockNode.getPath())-1);
zkClient.subscribeDataChanges(upNodePath, new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {}// If the previous node was deleted, try again to acquire the lock
@Override
public void handleDataDeleted(String s) throws Exception {
System.out.println("Node Deletion"+s);
Lock lock=tryActiveLock(lockNode);
synchronized (lockNode){
if (lock.isActive()){
lockNode.notify();
}
}
zkClient.unsubscribeDataChanges(upNodePath,this); }}); }return lockNode;
}
public Lock createLockNode(String lockId) {
String nodePath = zkClient.createEphemeralSequential(rootPath + "/" + lockId, "lock");
return newLock(lockId, nodePath); }}Copy the code
(4) Test
Add a variable to a thread without unlocking it:
public class Test {
private int flag=0;
private ZookeeperLock zookeeperLock=new ZookeeperLock();
@Test
public void testLock(a) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executorService.submit(()->{
flag++;
});
}
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.SECONDS); System.out.println(flag); }}Copy the code
The final result never reaches 100 because there is an update loss. Add lock:
public class Test {
private int flag=0;
private ZookeeperLock zookeeperLock=new ZookeeperLock();
@Test
public void testLock(a) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executorService.submit(()->{
Lock lock = zookeeperLock.lock("myLock".60 * 1000);
flag++;
zookeeperLock.unlock(lock);
});
}
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.SECONDS); System.out.println(flag); }}Copy the code
It’s always going to be 100.
(5) Summary
Once you understand the principle of distributed locking, the implementation of the code becomes very simple. You’re tired because you’re walking uphill! I’ll see you next time.