zookeeper java 锁_java搞懂分布式锁,应对一线大厂面试,zookeeper实现分布式锁
01线程同步机制
线程进行资源竞争
当有一个线程或进程在对资源进行操作时候,其他线程或资源都不可以对这个资源进行操作。直到线程或进程完成操作,其他线程或进程才能对该资源进行操作。剩下的线程或进程又处于等待状态。
线程进程同步的方法和机制
临界区:通过对线程串行化来访问公共资源或一段代码。
互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。
信号量:它允许多个任务在同一时刻访问同一资源,但是需要限制在同一时刻访问资源的最大线程数目。
事件:通过通知操作的方式来保持任务的同步,还可以方便实现对多个任务优先级比较的操作。
02分布式锁的实现技术
基于数据库实现分布式锁:性能较差,容易出现单点故障,锁没有失效时间,容易死锁。非阻塞的,不可重入。
基于缓存实现的分布式锁:锁没有失效时间,容易死锁。非阻塞式的,不可重入。
基于zookeepeer实现的分布式锁:实现相对简单,可靠性高,性能较好。
03Zookeeper介绍
Zookeeper是文件系统和监听通知机制的混合。只有叶子节点才能够存储数据。每个非根节点也可以存储数据。监听机制:客户端注册监听它关心的目录节点,当目录节点发生变化时zk会通知客户端。
Zk的节点类型
Persistent:持久化目录节点,客户端与zk服务端断开连接后,该节点仍旧存在。
Persistent-sequential:持久化顺序编号目录节点,客户端与zk服务端断开连接后,该节点仍旧存在,只是zk给该节点名称进行顺序编号。
Ephemeral:临时目录节点:客户端断开连接后,该节点被删除。
Ephemeral-sequential:临时顺序编号目录节点,客户端断开连接后,该节点被删除。,只是zk给该节点名称进行顺序编号。
对于持久化和临时节点,同一个znode下,节点的名称是唯一的。

04Java并发锁实现
场景描述:在线程高并发下,生产有一定业务含义的订单编号时间+序列号。
创建一个生成订单号的工具类
public class OrderCoderGenerator {
private static int i=0;
public String getOrderCode(){
Date now =new Date();
SimpleDateFormatsimpleDateFormat=new SimpleDateFormat("yyyyMMddHHmmss");
return simpleDateFormat.format(now)+ ++i;
}
}
创建服务调用
@Slf4j
public class OrderServerImpl implements Runnable{
private static OrderCoderGenerator ong=new OrderCoderGenerator();
//同时并发的线程数
private static final int NUM=10;
//按照线程初始化倒计数器
private static CountDownLatch cd1=new CountDownLatch(NUM);
private static Lock lock=new ReentrantLock();//实现线程同步
//创建订单接口
public void createOrder(){
String orderCode=null;
lock.lock();//避免订单号重复
try{orderCode=ong.getOrderCode();}catch(Exception e){e.printStackTrace();}finally {
lock.unlock();
}
log.info(Thread.currentThread().getName()+orderCode);
}
@Override
public void run() {
try {
cd1.await();
}catch (InterruptedException e){
e.printStackTrace();
}
//创建订单
createOrder();
}
public static void main(String[] args) {
for(int i=1;i<=NUM;i++){
new Thread(new OrderServerImpl()).start();
}
cd1.countDown();
}
}
05Zk分布式锁手写
@Slf4j
public class ZookeeperLock implements Lock {
private static final String ZK_IP_PORT="192.168.1.62:2181";
private static final String LOCK_NODE="/LOCK";
private ZKClient client=new ZKClient();
//同时并发的线程数
private static final int NUM=10;
//按照线程初始化倒计数器
private static CountDownLatch cdl=null;
/**
* 阻塞式加锁
*/
@Override
public void lock() {
if(tryLock()){
return;
}
waitForLock();
lock();
}
private void waitForLock() {
//给节点加监听
IZKDataListener listener=new IZKDataListener(){
@Override
public void handleDataChange(String path,Object data) throws Exception{}
@Override
public void handleDataChange(String path) throws Exception{
log.info("获取唤醒事件");
if(cdl!=null){
cdl.countDown();
}
}
};
//解除监听
client.unsubscribeDataChanges(LOCK_NODE),listener);
if(client.exists(LOCK_NODE)){
cdl=new new CountDownLatch(1);
try {
cdl.await();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
@Override
public boolean tryLock() {
try{client.createPersistent(LOCK_NODE);
return true;
} catch (Exception e){
return false;
}
}
@Override
public void unlock() {
client.delete(LOCK_NODE);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
}
06这样写带来的问题?
客户端无端接受了很多与自己无关的事件通知,存在死锁的可能性场景,在集群较大的环境带来危害,巨大的服务器性能损耗,网络冲击,可能造成宕机。
改进:不同时监听同一个节点,创建n个临时节点,只监听前面几个节点。剩下节点监听前面节点。只有序号最小的节点才能够加到锁。
更多推荐

所有评论(0)