01线程同步机制

线程进行资源竞争

当有一个线程或进程在对资源进行操作时候,其他线程或资源都不可以对这个资源进行操作。直到线程或进程完成操作,其他线程或进程才能对该资源进行操作。剩下的线程或进程又处于等待状态。

线程进程同步的方法和机制

临界区:通过对线程串行化来访问公共资源或一段代码。

互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。

信号量:它允许多个任务在同一时刻访问同一资源,但是需要限制在同一时刻访问资源的最大线程数目。

事件:通过通知操作的方式来保持任务的同步,还可以方便实现对多个任务优先级比较的操作。

02分布式锁的实现技术

基于数据库实现分布式锁:性能较差,容易出现单点故障,锁没有失效时间,容易死锁。非阻塞的,不可重入。

基于缓存实现的分布式锁:锁没有失效时间,容易死锁。非阻塞式的,不可重入。

基于zookeepeer实现的分布式锁:实现相对简单,可靠性高,性能较好。

03Zookeeper介绍

Zookeeper是文件系统和监听通知机制的混合。只有叶子节点才能够存储数据。每个非根节点也可以存储数据。监听机制:客户端注册监听它关心的目录节点,当目录节点发生变化时zk会通知客户端。

Zk的节点类型

Persistent:持久化目录节点,客户端与zk服务端断开连接后,该节点仍旧存在。

Persistent-sequential:持久化顺序编号目录节点,客户端与zk服务端断开连接后,该节点仍旧存在,只是zk给该节点名称进行顺序编号。

Ephemeral:临时目录节点:客户端断开连接后,该节点被删除。

Ephemeral-sequential:临时顺序编号目录节点,客户端断开连接后,该节点被删除。,只是zk给该节点名称进行顺序编号。

对于持久化和临时节点,同一个znode下,节点的名称是唯一的。

8b547f8cb453b97dbe2816f72b6082ee.png

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个临时节点,只监听前面几个节点。剩下节点监听前面节点。只有序号最小的节点才能够加到锁。

Logo

更多推荐