Zookeeper实现分布式锁

什么是多线程

多线程为了能够提高应用程序的运行效率,在一个进程中有多条不同的执行路径,同时并行执行,互不影响。

什么是线程安全

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

解决办法

 使用同步代码块或者Lock锁机制,保证在多个线程共享同一个变量只能有一个线程进行操作

什么是Java内存模型

共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

 

分布式锁解决办法:

业务场景

在分布式情况,生成全局订单号ID

生成订单号方案

  1. 使用时间戳
  2. 使用UUID
  3. 推特 (Twitter) 的 Snowflake 算法——用于生成唯一 ID

生成订单类:

//生成订单类

public class OrderNumGenerator {

    //全局订单id

      public static int count = 0;

 

      public String getNumber() {

           try {

                 Thread.sleep(200);

           } catch (Exception e) {

           }

           SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

           return simpt.format(new Date()) + "-" + ++count;

      }

}

使用多线程情况模拟生成订单号:

//使用多线程模拟生成订单号

public class OrderService implements Runnable {

     private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

 

     public void run() {

         getNumber();

     }

 

     public void getNumber() {

         String number = orderNumGenerator.getNumber();

         System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);

     }

 

     public static void main(String[] args) {

         System.out.println("####生成唯一订单号###");

         for (int i = 0; i < 100; i++) {

              new Thread(new OrderService()).start();

         }

 

     }

}

多线程生成订单号,线程安全问题解决:

使用synchronized或者local锁

Synchronized同步代码块方式:

//使用多线程模拟生成订单号

public class OrderService implements Runnable {

      private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

 

      public void run() {

           getNumber();

      }

 

      public void getNumber() {

           synchronized (this) {

                 String number = orderNumGenerator.getNumber();

                 System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);

           }

      }

 

      public static void main(String[] args) {

           System.out.println("####生成唯一订单号###");

           OrderService orderService = new OrderService();

           for (int i = 0; i < 100; i++) {

                 new Thread(orderService).start();

           }

 

      }

}

Lock锁方式:

public class OrderService implements Runnable {

      private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

      // 使用lock

      private java.util.concurrent.locks.Lock lock = new ReentrantLock();

 

      public void run() {

           getNumber();

      }

 

      public void getNumber() {

           try {

                 // synchronized (this) {

                 lock.lock();

                 String number = orderNumGenerator.getNumber();

                 System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);

                 // }

 

           } catch (Exception e) {

 

           } finally {

                 lock.unlock();

           }

      }

 

      public static void main(String[] args) {

           System.out.println("####生成唯一订单号###");

           OrderService orderService = new OrderService();

           for (int i = 0; i < 100; i++) {

                 new Thread(orderService).start();

           }

 

      }

}

分布式场景下生成订单ID:

业务场景

在分布式情况,生成全局订单号ID

产生问题

在分布式(集群)环境下,每台JVM不能实现同步,在分布式场景下使用时间戳生成订单号可能会重复

 

分布式情况下,怎么解决订单号生成不重复

  1. 使用分布式锁
  2. 提前生成好,订单号,存放在redis取。获取订单号,直接从redis中取。

使用分布式锁生成订单号技术

 

  1. 使用数据库实现分布式锁

操作数据时在数据中插入一条记录,如果其他线程操作的时候也去查一下数据库,如果有记录或者是有这条记录的版本信息,就进行等待,释放锁的时候也需要这条记录删除或修改。

缺点:性能差、线程出现异常时,容易出现死锁

    2.使用redis实现分布式锁

使用Setnx方法,和数据库类似

缺点:锁的失效时间难控制、容易产生死锁、非阻塞式、不可重入

  3.使用zookeeper实现分布式锁

实现相对简单、可靠性强、使用临时节点,失效时间容易控制

 

什么是分布式锁

分布式锁一般用在分布式系统或者多个应用中,用来控制同一任务是否执行或者任务的执行顺序。在项目中,部署了多个tomcat应用,在执行定时任务时就会遇到同一任务可能执行多次的情况,我们可以借助分布式锁,保证在同一时间只有一个tomcat应用执行了定时任务

 

使用Zookeeper实现分布式锁

Zookeeper实现分布式锁原理

使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……

maven:

  <dependencies>

         <dependency>

              <groupId>com.101tec</groupId>

              <artifactId>zkclient</artifactId>

              <version>0.10</version>

         </dependency>

     </dependencies>

创建Lock接口:

public interface Lock {

    //获取到锁的资源

     public void getLock();

    // 释放锁

     public void unLock();

}

 

创建ZookeeperAbstractLock抽象类:

//将重复代码写入子类中..

public abstract class ZookeeperAbstractLock implements Lock {

      // zk连接地址

      private static final String CONNECTSTRING = "127.0.0.1:2181";

      // 创建zk连接

      protected ZkClient zkClient = new ZkClient(CONNECTSTRING);

      protected static final String PATH = "/lock";

 

      public void getLock() {

           if (tryLock()) {

                 System.out.println("##获取lock锁的资源####");

           } else {

                 // 等待

                 waitLock();

                 // 重新获取锁资源

                 getLock();

           }

 

      }

 

      // 获取锁资源

      abstract boolean tryLock();

 

      // 等待

      abstract void waitLock();

 

      public void unLock() {

           if (zkClient != null) {

                 zkClient.close();

                 System.out.println("释放锁资源...");

           }

      }

 

}

ZookeeperDistrbuteLock类:

public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {

      private CountDownLatch countDownLatch = null;

 

      @Override

      boolean tryLock() {

           try {

                 zkClient.createEphemeral(PATH);

                 return true;

           } catch (Exception e) {

//               e.printStackTrace();

                 return false;

           }

 

      }

 

      @Override

      void waitLock() {

           IZkDataListener izkDataListener = new IZkDataListener() {

 

                 public void handleDataDeleted(String path) throws Exception {

                      // 唤醒被等待的线程

                      if (countDownLatch != null) {

                            countDownLatch.countDown();

                       }

                 }

                 public void handleDataChange(String path, Object data) throws Exception {

 

                 }

           };

           // 注册事件

           zkClient.subscribeDataChanges(PATH, izkDataListener);

           if (zkClient.exists(PATH)) {

                 countDownLatch = new CountDownLatch(1);

                 try {

                      countDownLatch.await();

                 } catch (Exception e) {

                      e.printStackTrace();

                 }

           }

           // 删除监听

           zkClient.unsubscribeDataChanges(PATH, izkDataListener);

      }

 

}

使用Zookeeper锁运行效果:

public class OrderService implements Runnable {

      private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

      // 使用lock

      // private java.util.concurrent.locks.Lock lock = new ReentrantLock();

      private Lock lock = new ZookeeperDistrbuteLock();

      public void run() {

           getNumber();

      }

      public void getNumber() {

           try {

                 lock.getLock();

                 String number = orderNumGenerator.getNumber();

                 System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);

           } catch (Exception e) {

                 e.printStackTrace();

           } finally {

                 lock.unLock();

           }

      }

      public static void main(String[] args) {

           System.out.println("####生成唯一订单号###");

//         OrderService orderService = new OrderService();

           for (int i = 0; i < 100; i++) {

                 new Thread( new OrderService()).start();

           }

      }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值