分布式配置
连接ZK集群
ZK 工具类(ZKUtils.java):
package org.garen.study.config;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* ZK 工具类
*
* 1、创建连接,返回ZK连接对象
*/
public class ZKUtils {
/**
* zk连接对象
*/
private static ZooKeeper zk;
/**
* 连接ZK集群的字符串,指定了父目录为 "/testConf"
*/
private static String connectString = "192.168.174.62:2181,192.168.174.63:2181,192.168.174.64:2181,192.168.174.62:2181/testConf";
/**
* 创建连接的watch
* defaultWatch,创建连接传入的watch,这个watch,session级别的,跟path、node没有关系。
*/
private static DefaultWatch defaultWatch = new DefaultWatch();
/**
* 阻塞对象
*/
private static CountDownLatch countDownLatch = new CountDownLatch(1);
/**
* 创建连接,返回连接对象
*/
public static ZooKeeper getZK() {
try {
// 创建连接对象,传入“连接ZK集群的字符串”、session超时时间、“创建连接的watch”
zk = new ZooKeeper(connectString, 1000, defaultWatch);
// 创建连接的watch对象传入“阻塞对象”
defaultWatch.setCountDownLatch(countDownLatch); // set countDownLatch
// 阻塞,直到ZK连接对象创建完成
countDownLatch.await();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
// 返回连接对象
return zk;
}
}
创建连接的watch(DefaultWatch.java)
package org.garen.study.config;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import java.util.concurrent.CountDownLatch;
/**
* 创建连接的watch
* defaultWatch,创建连接传入的watch,这个watch,session级别的,跟path、node没有关系。
*/
public class DefaultWatch implements Watcher {
/**
* 阻塞对象
*/
CountDownLatch countDownLatch;
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
/**
* DefaultWatch 的回调方法
* @param event 事件
*/
@Override
public void process(WatchedEvent event) {
// defaultWatch触发回调,打印事件信息
System.out.println("Connect zk cluster, create zk watch: " + event.toString());
// 遍历时间状态
switch (event.getState()) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
case SyncConnected:
// 连接成功,结束阻塞
countDownLatch.countDown();
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
case Closed:
break;
}
}
}
测试
测试类(TestConfig.java):
其中conn()方法,创建连接,返回连接对象,见前面“连接ZK集群”章节的代码
package org.garen.study.config;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* 分布式配置,测试类
*/
public class TestConfig {
// ZK集群连接对象
ZooKeeper zk;
/**
* 运行测试方法前,创建ZK集群连接,获得连接对象
*/
@Before
public void conn() {
zk = ZKUtils.getZK(); // 创建连接,返回连接对象
}
/**
* 结束测试方法后,关闭ZK集群连接
*/
@After
public void close() {
try {
zk.close(); // 关闭ZK集群连接
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 分布式配置,测试方法
*/
@Test
public void getConf() {
// 配置文件对象类
MyConf myConf = new MyConf();
// 回调方法工具类
WatchCallBack watchCallBack = new WatchCallBack();
watchCallBack.setZk(zk); // ZK连接对象赋值
watchCallBack.setMyConf(myConf); // 配置文件对象赋值
// 阻塞,直到myConf有值,结束阻塞,程序继续向下执行
// 情况1:节点存在
// 情况2:节点不存在
// 情况3:节点不存在,阻塞,然后,节点被创建了
// 情况4:节点存在,节点被改变了
// 情况5:节点存在,然后节点被删除了
watchCallBack.awaitMyConf();
// 死循环,打印配置
while (true) {
// 配置为空(节点被删除了,配置清空了)
if(myConf.getConf() == null)
{
System.out.println("no conf ..."); // 打印没有配置的消息
watchCallBack.awaitMyConf(); // 阻塞,直到myConf有值,结束阻塞,程序继续向下执行
}
// 配置不为空
else
{
System.out.println(myConf.getConf()); // 打印配置信息
}
// 每次循环,睡2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
配置文件对象类(MyConf.java)
package org.garen.study.config;
/**
* 配置文件对象类
*/
public class MyConf {
/**
* 配置的一个字符串属性
*/
private String conf;
public String getConf() {
return conf;
}
public void setConf(String conf) {
this.conf = conf;
}
}
回调方法工具类(WatchCallBack.java)
package org.garen.study.config;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
/**
* 回调方法工具类
*
* Watcher 的回调方法
* AsyncCallback.StatCallback 的回调方法
* AsyncCallback.DataCallback 的回调方法
*/
public class WatchCallBack implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
/**
* ZK连接对象
*/
ZooKeeper zk;
/**
* 配置文件对象
*/
MyConf myConf;
/**
* 阻塞对象
*/
CountDownLatch countDownLatch = new CountDownLatch(1);
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public void setMyConf(MyConf myConf) {
this.myConf = myConf;
}
/**
* 阻塞,直到 myConf有值,结束阻塞,程序继续向下执行
*
* 情况1:节点存在
* watcher没有被触发
* 执行回调方法 cb: this -> AsyncCallback.StatCallback
*
* 情况2:节点不存在
* watcher没有被触发
* 执行回调方法 cb: this -> AsyncCallback.StatCallback
*
* 情况3:节点不存在,阻塞,然后,节点被创建了
* watcher被触发
* 创建走NodeCreated
*
* 情况4:节点存在,节点被改变了
* 节点存在,同情况1
* 节点被改变了,watcher被触发
* 改变走NodeDataChanged
*
* 情况5:节点存在,然后节点被删除了
* 节点存在,同情况1
* 节点被删除了,watcher被触发
* 删除走NodeDeleted,然后清空配置,重新创建阻塞对象,再调awaitMyConf()方法又会被阻塞
*/
public void awaitMyConf() {
// 节点是否存在
// 异步方法,通过回调处理结果,cb: this -> AsyncCallback.StatCallback 的回调方法 processResult
// 观察节点,如果发生变化,则通过watcher处理,watcher: this -> Watcher 的回调方法 process
zk.exists("/AppConf", this, this, "ABC");
try {
countDownLatch.await(); //阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Watcher 的回调方法
* @param event 事件
*/
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
// 节点被创建
// 查询节点值,通过回调处理结果,cb: this -> AsyncCallback.DataCallback 的回调方法 processResult
// 观察节点,如果发生变化,则通过watcher处理,watcher: this -> Watcher 的回调方法 process
zk.getData("/AppConf", this, this, "BCD");
break;
case NodeDeleted:
// 节点被删除了
// 清空配置
myConf.setConf(null);
// 重新创建阻塞对象,再调awaitMyConf()方法又会被阻塞
countDownLatch = new CountDownLatch(1);
break;
case NodeDataChanged:
// 节点值被修改了
// 重新查询节点值,通过回调处理结果,cb: this -> AsyncCallback.DataCallback 的回调方法 processResult
// 观察节点,如果发生变化,则通过watcher处理,watcher: this -> Watcher 的回调方法 process
zk.getData("/AppConf", this, this, "BCD");
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
}
}
/**
* AsyncCallback.StatCallback 的回调方法
*
* 情况1:节点存在
* stat != null
* 查询节点值zk.getData,走回调处理结果;添加watcher,如果节点发生变化,触发watcher
*
* 情况2:节点不存在,stat为空,什么都不做
*
*
* @param rc
* @param path
* @param ctx
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
if(stat != null){
zk.getData("/AppConf", this, this, "BCD");
}
}
/**
* AsyncCallback.DataCallback 的回调方法
*
* 情况1:节点值不为空
* data != null
* 节点值存入配置,结束阻塞
*
* 情况2:节点值为空
* data == null
* 什么都不做
*
* @param rc
* @param path
* @param ctx
* @param data
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
if(data != null){
String s = new String(data);
myConf.setConf(s);
// 结束阻塞
countDownLatch.countDown();
}
}
}
运行代码进行测试
- 节点存在
- 删除节点
- 创建节点
- 修改节点
分布式锁
连接ZK集群
ZK 工具类(ZKUtils.java):
延用“分布式配置”中的ZKUtils.java,连接ZK集群的字符串,父节点改成了“/testLock”。
/**
* 连接ZK集群的字符串,指定了父目录为 "/testConf"
*/
// private static String connectString = "192.168.174.62:2181,192.168.174.63:2181,192.168.174.64:2181,192.168.174.62:2181/testConf";
/**
* 连接ZK集群的字符串,指定了父目录为 "/testLock"
*/
private static String connectString = "192.168.174.62:2181,192.168.174.63:2181,192.168.174.64:2181,192.168.174.62:2181/testLock";
创建连接的watch(DefaultWatch.java)
延用“分布式配置”中的DefaultWatch.java,没有任何改变。
测试
测试类(TestLock.java):
其中conn()方法,创建连接,返回连接对象,见前面“连接ZK集群”章节的代码。
package org.garen.study.lock;
import org.apache.zookeeper.ZooKeeper;
import org.garen.study.config.ZKUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.stream.IntStream;
/**
* 分布式锁,测试类
*/
public class TestLock {
// ZK集群连接对象
ZooKeeper zk;
/**
* 运行测试方法前,创建ZK集群连接,获得连接对象
*/
@Before
public void conn() {
zk = ZKUtils.getZK(); // 创建连接,返回连接对象
}
/**
* 结束测试方法后,关闭ZK集群连接
*/
@After
public void close() {
try {
zk.close(); // 关闭ZK集群连接
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 分布式锁,测试方法
*/
@Test
public void testLock() {
// 循环10次,创建10个线程
IntStream.range(0, 10).forEach(i -> {
// 每次循环,创建1个线程
new Thread(() -> {
// 当前线程名称
String threadName = Thread.currentThread().getName();
// 回调方法工具类
WatchCallBack watchCallBack = new WatchCallBack();
watchCallBack.setZk(zk); // ZK连接对象赋值
watchCallBack.setThreadName(threadName); // 线程名称赋值
// 枪锁
watchCallBack.tryLock();
// 干活
System.out.println(threadName + "working...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 释放锁
watchCallBack.unLock();
}).start();
});
// 主线程不停
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
回调方法工具类(WatchCallBack.java)
package org.garen.study.lock;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 回调方法工具类
*/
public class WatchCallBack implements Watcher, AsyncCallback.StringCallback, AsyncCallback.Children2Callback, AsyncCallback.StatCallback {
/**
* ZK连接对象
*/
ZooKeeper zk;
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
/**
* 线程名称
*/
String threadName;
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
/**
* 阻塞对象
*/
CountDownLatch countDownLatch = new CountDownLatch(1);
/**
* 每个线程创建的,以"/lock"开头的,序列化临时节点,的路径
*/
String pathName;
public String getPathName() {
return pathName;
}
public void setPathName(String pathName) {
this.pathName = pathName;
}
/**
* 抢锁
*
* 在父节点("/testLock")下创建以"/lock"开头的序列化临时节点
* 阻塞
*
* 后续方法,看 -> zk.create 的回调
*/
public void tryLock() {
try {
System.out.println(threadName + " create....");
// 在父节点("/testLock")下创建以"/lock"开头的序列化临时节点
zk.create("/lock", threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL, this, "abc");
// 阻塞
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 释放锁
*
* 删除,当前线程创建的,以"/lock"开头的,序列化临时节点
*
* 后续方法,看 -> Watcher 的回调方法(是第一个节点)
*/
public void unLock() {
try {
zk.delete(pathName, -1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* Watcher 的回调方法
* 监控删除事件
*
* 如果第一个节点,也就是锁释放了,其实只有第二个节点收到了回调事件!
*
* 如果,不是第一个节点,某一个线程挂了(临时节点,客户端session会话结束就消失了),
* 也能造成他后面的收到这个通知,从而让他后面那个节点去watch挂掉这个节点前边的。
*
* @param event
*/
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
// 查询子节点,不监控,回调方法处理结果
// 后续方法,看 ->zk.getChildren 的回调方法
zk.getChildren("/", false, this, "sdf");
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
}
}
/**
* AsyncCallback.StringCallback 的回调方法
* zk.create的回调方法
*
* 打印消息:哪个线程,创建了哪个节点
* 给pathName赋值
* 查询子节点,不监控,回调方法处理结果
*
* 后续方法,看 -> zk.getChildren 的回调
*
* @param rc
* @param path
* @param ctx
* @param name
*/
@Override
public void processResult(int rc, String path, Object ctx, String name) {
// 获得锁要看自己是不是最小的
// 监控前一个,所以父节点不需要watcher
if(name != null){
System.out.println(threadName + "create node : " + name);
pathName = name;
// 查询子节点,不监控,回调方法处理结果
zk.getChildren("/", false, this, "sdf");
}
}
/**
* AsyncCallback.Children2Callback 的回调方法
* zk.getChildren的回调
*
* 排序子节点,然后查询当前线程创建的节点,在子节点集合中的排序
* 如果排序为0(第一个节点),则抢到锁,结束阻塞
* 后续方法,看 -> 测试方法 testLock() 中 watchCallBack.tryLock() 抢锁之后的代码
* 如果排序不是0(不是第一个节点),则没有抢到锁,通过调用zk.exists方法上,给前一个节点加上监控watch
* 后续方法,看 -> Watcher 的回调方法(不是第一个节点)
* @param rc
* @param path
* @param ctx
* @param children 每个线程创建的,以"/lock"开头的,序列化临时节点的集合
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
// 一定能看到自己前面的
// System.out.println(threadName + "look locks ...");
// for (String child : children) {
// System.out.println(child);
// }
// children 排序
Collections.sort(children);
// pathName 当前线程,创建的,以"/lock"开头的,序列化临时节点
// pathName.substring(1) 去掉"/" -> child
// child 在 children 里的排序
int i = children.indexOf(pathName.substring(1));
// 是不是第一个,设定第一个抢到锁
if(i == 0) {
// 是第一个,抢到了锁
System.out.println(threadName + " i am first ...");
try {
// 父节点("/testLock")的值,设置为:抢到锁的,线程的名称
zk.setData("/", threadName.getBytes(), -1);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 抢到锁的线程,结束阻塞
countDownLatch.countDown();
}else {
// 不是第一个,没有抢到锁
// 前一个节点是否存在,回调方法
// 增加对前一个节点的监控
zk.exists("/" + children.get(i-1), this, this, "ggg");
}
}
/**
* AsyncCallback.StatCallback 的回调方法
* zk.exists 的回调方法
* 前一个节点是否存在
*
* @param rc
* @param path
* @param ctx
* @param stat
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println("last node exists : [" + "rc:" + rc + ", path: " + path + ", ctx: " + ctx + ", stat: " + stat + "]");
}
}
运行代码进行测试
-
运行前
-
运行结果
D:\GarenGosling\Java\jdk1.8.0_291\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\GarenGosling\JetBrains\IntelliJ IDEA Educational Edition 2020.2.1\lib\idea_rt.jar=51228:D:\GarenGosling\JetBrains\IntelliJ IDEA Educational Edition 2020.2.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\GarenGosling\JetBrains\IntelliJ IDEA Educational Edition 2020.2.1\lib\idea_rt.jar;D:\GarenGosling\JetBrains\IntelliJ IDEA Educational Edition 2020.2.1\plugins\junit\lib\junit5-rt.jar;D:\GarenGosling\JetBrains\IntelliJ IDEA Educational Edition 2020.2.1\plugins\junit\lib\junit-rt.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\charsets.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\deploy.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\access-bridge-64.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\cldrdata.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\dnsns.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\jaccess.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\jfxrt.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\localedata.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\nashorn.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\sunec.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\sunjce_provider.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\sunmscapi.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\sunpkcs11.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\ext\zipfs.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\javaws.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\jce.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\jfr.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\jfxswt.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\jsse.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\management-agent.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\plugin.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\resources.jar;D:\GarenGosling\Java\jdk1.8.0_291\jre\lib\rt.jar;D:\GarenGosling\workspace\idea_study\study-03\target\test-classes;D:\GarenGosling\workspace\idea_study\study-03\target\classes;D:\GarenGosling\apache-maven-3.6.3\repo\junit\junit\4.11\junit-4.11.jar;D:\GarenGosling\apache-maven-3.6.3\repo\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\GarenGosling\apache-maven-3.6.3\repo\org\apache\zookeeper\zookeeper\3.5.9\zookeeper-3.5.9.jar;D:\GarenGosling\apache-maven-3.6.3\repo\org\apache\zookeeper\zookeeper-jute\3.5.9\zookeeper-jute-3.5.9.jar;D:\GarenGosling\apache-maven-3.6.3\repo\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;D:\GarenGosling\apache-maven-3.6.3\repo\io\netty\netty-handler\4.1.50.Final\netty-handler-4.1.50.Final.jar;D:\GarenGosling\apache-maven-3.6.3\repo\io\netty\netty-common\4.1.50.Final\netty-common-4.1.50.Final.jar;D:\GarenGosling\apache-maven-3.6.3\repo\io\netty\netty-resolver\4.1.50.Final\netty-resolver-4.1.50.Final.jar;D:\GarenGosling\apache-maven-3.6.3\repo\io\netty\netty-buffer\4.1.50.Final\netty-buffer-4.1.50.Final.jar;D:\GarenGosling\apache-maven-3.6.3\repo\io\netty\netty-transport\4.1.50.Final\netty-transport-4.1.50.Final.jar;D:\GarenGosling\apache-maven-3.6.3\repo\io\netty\netty-codec\4.1.50.Final\netty-codec-4.1.50.Final.jar;D:\GarenGosling\apache-maven-3.6.3\repo\io\netty\netty-transport-native-epoll\4.1.50.Final\netty-transport-native-epoll-4.1.50.Final.jar;D:\GarenGosling\apache-maven-3.6.3\repo\io\netty\netty-transport-native-unix-common\4.1.50.Final\netty-transport-native-unix-common-4.1.50.Final.jar;D:\GarenGosling\apache-maven-3.6.3\repo\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;D:\GarenGosling\apache-maven-3.6.3\repo\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;D:\GarenGosling\apache-maven-3.6.3\repo\log4j\log4j\1.2.17\log4j-1.2.17.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 org.garen.study.lock.TestLock,testLock
log4j:WARN No appenders could be found for logger (org.apache.zookeeper.ZooKeeper).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Connect zk cluster, create zk watch: WatchedEvent state:SyncConnected type:None path:null
Thread-1 create....
Thread-4 create....
Thread-5 create....
Thread-0 create....
Thread-9 create....
Thread-8 create....
Thread-7 create....
Thread-3 create....
Thread-2 create....
Thread-6 create....
Thread-3create node : /lock0000000030
Thread-4create node : /lock0000000031
Thread-7create node : /lock0000000032
Thread-6create node : /lock0000000033
Thread-8create node : /lock0000000034
Thread-0create node : /lock0000000035
Thread-5create node : /lock0000000036
Thread-2create node : /lock0000000037
Thread-9create node : /lock0000000038
Thread-1create node : /lock0000000039
Thread-3 i am first ...
Thread-3working...
last node exists : [rc:0, path: /lock0000000030, ctx: ggg, stat: 42949673243,42949673243,1627737835099,1627737835099,0,0,0,0,8,0,42949673243
]
last node exists : [rc:0, path: /lock0000000031, ctx: ggg, stat: 42949673244,42949673244,1627737835100,1627737835100,0,0,0,0,8,0,42949673244
]
last node exists : [rc:0, path: /lock0000000032, ctx: ggg, stat: 42949673245,42949673245,1627737835101,1627737835101,0,0,0,0,8,0,42949673245
]
last node exists : [rc:0, path: /lock0000000033, ctx: ggg, stat: 42949673246,42949673246,1627737835101,1627737835101,0,0,0,0,8,0,42949673246
]
last node exists : [rc:0, path: /lock0000000034, ctx: ggg, stat: 42949673247,42949673247,1627737835101,1627737835101,0,0,0,0,8,0,42949673247
]
last node exists : [rc:0, path: /lock0000000035, ctx: ggg, stat: 42949673248,42949673248,1627737835101,1627737835101,0,0,0,0,8,0,42949673248
]
last node exists : [rc:0, path: /lock0000000036, ctx: ggg, stat: 42949673249,42949673249,1627737835101,1627737835101,0,0,0,0,8,0,42949673249
]
last node exists : [rc:0, path: /lock0000000037, ctx: ggg, stat: 42949673250,42949673250,1627737835101,1627737835101,0,0,0,0,8,0,42949673250
]
last node exists : [rc:0, path: /lock0000000038, ctx: ggg, stat: 42949673251,42949673251,1627737835101,1627737835101,0,0,0,0,8,0,42949673251
]
Thread-4 i am first ...
Thread-4working...
Thread-7 i am first ...
Thread-7working...
Thread-6 i am first ...
Thread-6working...
Thread-8 i am first ...
Thread-8working...
Thread-0 i am first ...
Thread-0working...
Thread-5 i am first ...
Thread-5working...
Thread-2 i am first ...
Thread-2working...
Thread-9 i am first ...
Thread-9working...
Thread-1 i am first ...
Thread-1working...
Process finished with exit code -1
- 运行中
- 运行后
至此,本专栏结束!