ZooKeeper分布式專題(七)-- 使用zookeeper實現(xiàn)分布式鎖

ZooKeeper分布式專題與Dubbo微服務(wù)入門

zookeeper實現(xiàn)分布式鎖

什么多線程

多線程為了能夠提高應(yīng)用程序的運行效率,在一個進程中有多條不同的執(zhí)行路徑,同時并行執(zhí)行,互不影響。

這里關(guān)于線程的介紹就不多闡述,想了解更多關(guān)于線程的介紹請移步 https://github.com/haoxiaoyong1014/recording

下面我們只針對分布式環(huán)境下實現(xiàn)分布式鎖介紹;

什么是java內(nèi)存模型

共享內(nèi)存模型指的就是Java內(nèi)存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化

image.png

從上圖來看,線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個步驟:

  1. 首先,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去。

  2. 然后,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量。

image.png

如上圖所示,本地內(nèi)存A和B有主內(nèi)存中共享變量x的副本。假設(shè)初始時,這三個內(nèi)存中的x值都為0。線程A在執(zhí)行時,把更新后的x值(假設(shè)值為1)臨時存放在自己的本地內(nèi)存A中。當(dāng)線程A和線程B需要通信時,線程A首先會把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中,此時主內(nèi)存中的x值變?yōu)榱?。隨后,線程B到主內(nèi)存中去讀取線程A更新后的x值,此時線程B的本地內(nèi)存的x值也變?yōu)榱?。
從整體來看,這兩個步驟實質(zhì)上是線程A在向線程B發(fā)送消息,而且這個通信過程必須要經(jīng)過主內(nèi)存。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為java程序提供內(nèi)存可見性保證。

總結(jié):什么是Java內(nèi)存模型:java內(nèi)存模型簡稱jmm,定義了一個線程對另一個線程可見。共享變量存放在主內(nèi)存中,每個線程都有自己的本地內(nèi)存,當(dāng)多個線程同時訪問一個數(shù)據(jù)的時候,可能本地內(nèi)存沒有及時刷新到主內(nèi)存,所以就會發(fā)生線程安全問題。

傳統(tǒng)方式生成訂單號ID

生成訂單類

public class OrderNumGenerator {

    //全局訂單id;
    public static int count = 0;

    public String getNumber() {

        try {
            //TimeUnit.SECONDS.sleep(2);
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        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();
        }

    }
}

這時候會出現(xiàn)線程安全問題;

image.png

下面解決這種線程安全問題的方式有很多

例如:使用synchronized或者lock鎖

這里對synchronized就不做過的說明了。想了解更多關(guān)于synchronized的語義及使用請移步 https://github.com/haoxiaoyong1014/recording

使用lock鎖解決線程安全問題:

生成訂單類

public class OrderNumGenerator {

    //全局訂單id;
    public static int count = 0;

    public String getNumber() {

        try {
            //TimeUnit.SECONDS.sleep(2);
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
        return simpt.format(new Date()) + "-" + ++count;
    }
}

沒有做任何的改變;

使用多線程情況模擬生成訂單號(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 {
            lock.lock();
            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(orderService).start();
        }

    }
}

對比和之前和那些改變?

lock.lock();上鎖,

lock.unlock();釋放鎖

同時我們要注意main方法中的OrderService對象;這里只實例化一次;

image.png

很完美的打印到100;

下面介紹在分布式環(huán)境下生成訂單ID;

在分布式(集群)環(huán)境下,每臺JVM不能實現(xiàn)同步,在分布式場景下使用時間戳生成訂單號可能會重復(fù)

使用分布式鎖生成訂單號技術(shù)

1.使用數(shù)據(jù)庫實現(xiàn)分布式鎖
缺點:性能差、線程出現(xiàn)異常時,容易出現(xiàn)死鎖
2.使用redis實現(xiàn)分布式鎖
缺點:鎖的失效時間難控制、容易產(chǎn)生死鎖、非阻塞式、不可重入
3.使用zookeeper實現(xiàn)分布式鎖
實現(xiàn)相對簡單、可靠性強、使用臨時節(jié)點,失效時間容易控制

什么是分布式鎖

分布式鎖一般用在分布式系統(tǒng)或者多個應(yīng)用中,用來控制同一任務(wù)是否執(zhí)行或者任務(wù)的執(zhí)行順序。在項目中,部署了多個tomcat應(yīng)用,在執(zhí)行定時任務(wù)時就會遇到同一任務(wù)可能執(zhí)行多次的情況,我們可以借助分布式鎖,保證在同一時間只有一個tomcat應(yīng)用執(zhí)行了定時任務(wù)

使用Zookeeper實現(xiàn)分布式鎖

Zookeeper實現(xiàn)分布式鎖原理

使用zookeeper創(chuàng)建臨時序列節(jié)點來實現(xiàn)分布式鎖,適用于順序執(zhí)行的程序,大體思路就是創(chuàng)建臨時序列節(jié)點,找出最小的序列節(jié)點,獲取分布式鎖,程序執(zhí)行完成之后此序列節(jié)點消失,通過watch來監(jiān)控節(jié)點的變化,從剩下的節(jié)點的找到最小的序列節(jié)點,獲取分布式鎖,執(zhí)行相應(yīng)處理,依次類推……

添加依賴

<dependency>
   <groupId>com.101tec</groupId>
   <artifactId>zkclient</artifactId>
   <version>0.10</version>
</dependency>

創(chuàng)建Lock接口

public interface Lock {

    //獲取到鎖的資源
    void getLock();
    // 釋放鎖
    void unLock();

}

創(chuàng)建ZookeeperAbstractLock抽象類

public abstract class ZookeeperAbstractLock implements Lock {

    // zk連接地址
    private static final String CONNECTSTRING = "127.0.0.1:2181";

    // 創(chuàng)建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;

    boolean tryLock() {

        try {
            zkClient.createEphemeral(PATH);
            return true;
        } catch (Exception e) {
//       e.printStackTrace();
            return false;
        }
    }

    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();
            }
        }
        // 刪除監(jiān)聽
        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();
      }
   }
}

執(zhí)行main方法:

##獲取lock鎖的資源####
Thread-1,生成訂單ID:2019-08-19-22-32-50-1
釋放鎖資源...
##獲取lock鎖的資源####
Thread-3,生成訂單ID:2019-08-19-22-32-59-2
釋放鎖資源...
##獲取lock鎖的資源####
Thread-5,生成訂單ID:2019-08-19-22-33-08-3
釋放鎖資源...
##獲取lock鎖的資源####
Thread-7,生成訂單ID:2019-08-19-22-33-17-4
釋放鎖資源...
##獲取lock鎖的資源####
Thread-9,生成訂單ID:2019-08-19-22-33-26-5
釋放鎖資源...
##獲取lock鎖的資源####
Thread-11,生成訂單ID:2019-08-19-22-33-35-6
釋放鎖資源...
##獲取lock鎖的資源####
Thread-13,生成訂單ID:2019-08-19-22-33-44-7
釋放鎖資源...
##獲取lock鎖的資源####
Thread-15,生成訂單ID:2019-08-19-22-33-53-8
釋放鎖資源...
##獲取lock鎖的資源####
Thread-17,生成訂單ID:2019-08-19-22-34-02-9
釋放鎖資源...
##獲取lock鎖的資源####
Thread-19,生成訂單ID:2019-08-19-22-34-11-10
釋放鎖資源...
##獲取lock鎖的資源####
·····
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容