通常实现分布式锁的几种方式:
1.redis setNX存在则会返回0
2.数据库方式去实现,通过索引唯一的方式
3.zookeeper实现
多个线程同时往一个根节点下插入临时有序节点,zookeeper底层会为节点维护顺序,节点名小的就是先插入的节点,即先获取锁的线程,后面的线程只有等待前面一个线程释放锁后才可以按节点顺序依次获取锁。
这里我们用zookeeper原生API实现分布式锁:
注释都写在了代码里了,想运行的copy下代码改下集群地址即可。欢迎留言指出不足。
首先来两个工具类:
1.获取zookeeper会话的工具类:
public class ZookeeperClient {
private final static String CONNECTSTRING="192.168.74.131:2181,192.168.74.132:2181,192.168.74.133:2181";
private static int sessionTimeout=5000;
//获取连接
public static ZooKeeper getInstance() throws IOException, InterruptedException {
final CountDownLatch conectStatus=new CountDownLatch(1);
ZooKeeper zooKeeper=new ZooKeeper(CONNECTSTRING, sessionTimeout, new Watcher() {
public void process(WatchedEvent event) {
if(event.getState()== Event.KeeperState.SyncConnected){
conectStatus.countDown();
}
}
});
conectStatus.await();
return zooKeeper;
}
public static int getSessionTimeout() {
return sessionTimeout;
}
}
创建一个监听锁删除的工具类:
public class LockWatcher implements Watcher{
private CountDownLatch latch;
public LockWatcher(CountDownLatch latch) {
this.latch = latch;
}
public void process(WatchedEvent event) {
if(event.getType()== Event.EventType.NodeDeleted){
latch.countDown();
}
}
}
实现简单的分布式锁的类:
public class DistributeLock {
private static final String ROOT_LOCKS="/LOCKS";//根节点
private ZooKeeper zooKeeper;
private int sessionTimeout; //会话超时时间
private String lockID; //记录锁节点id
private final static byte[] data={1,2}; //节点的数据
private CountDownLatch countDownLatch=new CountDownLatch(1);
public DistributeLock() throws IOException, InterruptedException {
this.zooKeeper=ZookeeperClient.getInstance();
this.sessionTimeout=ZookeeperClient.getSessionTimeout();
}
//获取锁的方法
public boolean lock(){
try {
//创建临时有序的开放节点
lockID=zooKeeper.create(ROOT_LOCKS+"/",data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(Thread.currentThread().getName()+"->成功创建了lock节点["+lockID+"], 开始去竞争锁");
List<String> childrenNodes=zooKeeper.getChildren(ROOT_LOCKS,true);//获取根节点下的所有子节点 并增加监控 此处会获取到10个节点
//排序,从小到大
SortedSet<String> sortedSet=new TreeSet<String>();
for(String children:childrenNodes){
sortedSet.add(ROOT_LOCKS+"/"+children);
}
//拿到最小的节点 最小的节点就是第一个拿到分布式锁的线程,zookeeper维护节点的顺序
String first=sortedSet.first();
if(lockID.equals(first)){
//表示当前就是最小的节点 那就是第一个成功获取锁的线程
System.out.println(Thread.currentThread().getName()+"->成功获得锁,lock节点为:["+lockID+"]");
return true;
}
//拿到比当前LOCKID这个节点更小的节点集合
SortedSet<String> lessThanLockId=sortedSet.headSet(lockID);
if(!lessThanLockId.isEmpty()){
//拿到小且最近的一个节点 也就是lockID=3的话拿到的就是2
String prevLockID=lessThanLockId.last();
//监听上一个节点的事件,如果上一个节点删除,则countDown
zooKeeper.exists(prevLockID,new LockWatcher(countDownLatch));
//设置等待时间sessionTimeout TimeUnit.MILLISECONDS 颗粒度是毫秒
countDownLatch.await(sessionTimeout, TimeUnit.MILLISECONDS);
//上面这段代码意味着如果会话超时或者节点被删除(释放)了
System.out.println(Thread.currentThread().getName()+" 成功获取锁:["+lockID+"]");
}
return true;
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
public boolean unlock(){
System.out.println(Thread.currentThread().getName()+"->开始释放锁:["+lockID+"]");
try {
//删除节点 就意味着释放锁
zooKeeper.delete(lockID,-1);
System.out.println("节点["+lockID+"]成功被删除");
return true;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
return false;
}
public static void main(String[] args) {
final CountDownLatch latch=new CountDownLatch(10);
Random random=new Random();
for(int i=0;i<10;i++){
new Thread(()->{
DistributeLock lock=null;
try {
lock=new DistributeLock();
latch.countDown();
//等10个线程都启动后,再开始争夺分布式锁
latch.await();
lock.lock();
Thread.sleep(random.nextInt(500));
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock!=null){
lock.unlock();
}
}
}).start();
}
}
}