主頁 > 軟體設計 > 分布式鎖的三種實作方式

分布式鎖的三種實作方式

2021-04-01 21:55:44 軟體設計

一、基本概念

1、引入
        傳統的鎖都是有JDK官方提供的鎖的解決方案,也就是說這些鎖只能在一個JVM行程內有效,我們把這種鎖叫做單體應用鎖,但是,在互聯網高速發展的今天,單體應用鎖能夠滿足我們的需求嗎?
新的閱讀體驗:http://www.zhouhong.icu/post/143

本篇文章所有代碼:https://github.com/Tom-shushu/Distributed-system-learning-notes/

2、互聯網系統架構的演進
        在互聯網系統發展之初,系統比較簡單,消耗資源小,用戶訪問量也比較少,我們只部署一個Tomcat應用就可以滿足需求,系統架構圖如下:         一個Tomcat可以看作是一個JVM行程,當大量的請求并發到達系統時,所有的請求都落在這唯一的一個Tomcat上,如果某些請求方法是需要加鎖的,比如:秒殺扣減庫存,是可以滿足需求的,這和我們前面章節所講的內容是一樣的,但是隨著訪問量的增加,導致一個Tomcat難以支撐,這時我們就要集群部署Tomcat,使用多個Tomcat共同支撐整個系統         上圖中,我們部署了兩個Tomcat,共同支撐系統,當一個請求到達系統時,首先會經過Nginx,Nginx主要是做負載轉發的,它會根據自己配置的負載均衡策略將請求轉發到其中的一個Tomcat中,當大量的請求并發訪問時,兩個Tomcat共同承擔所有的訪問量,這時,我們同樣在秒殺扣減庫存的場景中,使用單體應用鎖,還能夠滿足要求嗎?
3、單體應用鎖的局限性
        如上圖所示,在整個系統架構中,存在兩個Tomcat,每個Tomcat是一個JVM,在進行秒殺業務的時候,由于大家都在搶購秒殺商品,大量的請求同時到達系統,通過Nginx分發到兩個Tomcat上,我們通過一個極端的案例場景,可以更好地理解單體應用鎖的局限性,假如,秒殺商品的數量只有1個,這時,這些大量的請求當中,只有一個請求可以成功的搶到這個商品,這就需要在扣減庫存的方法上加鎖,扣減庫存的動作只能一個一個去執行,而不能同時去執行,如果同時執行,這1個商品可能同時被多個人搶到,從而產生超賣現象,加鎖之后,扣減庫存的動作一個一個去執行,凡是將庫存扣減為負數的,都拋出例外,提示該用戶沒有搶到商品,通過加鎖看似解決了秒殺的問題,但是事實上真的是這樣嗎?         我們看到系統中存在兩個Tomcat,我們加的鎖是JDK提供的鎖,這種鎖只能在一個JVM下起作用,也就是             在一個Tomcat內是沒有問題的,當存在兩個或兩個以上的Tomcat時,大量的并發請求分散到不同的Tomcat上,在每一個Tomcat中都可以防止并發的產生,但是在多個Tomcat之間,每個Tomcat中獲得鎖的這個請求,又產生了并發,從而產生超賣現象,這也就是單體應用鎖的局限性,它只能在一個JVM內加鎖,而不能從這個應用層面去加鎖, 那么這個問題如何解決呢?這就需要使用分布式鎖了,在整個應用層面去加鎖,什么是分布式鎖呢?我們怎么去使用分布式鎖呢?
4、什么是分布式鎖
        在說分布式鎖之前,我們看一看單體應用鎖的特點,單體應用鎖是在一個JVM行程內有效,無法跨JVM、跨行程,那么分布式鎖的定義就出來了,分布式鎖就是可以跨越多個JVM、跨越多個行程的鎖,這種鎖就叫做分布式鎖,
5、分布式鎖的設計思路
        在上圖中,由于Tomcat是由Java啟動的,所以每個Tomcat可以看成一個JVM,JVM內部的鎖是無法跨越多個行程的,所以,我們要實作分布式鎖,我們只能在這些JVM之外去尋找,通過其他的組件來實作分布式鎖,系統的架構如圖所示:

        兩個Tomcat通過第三方的組件實作跨JVM、跨行程的分布式鎖,這就是分布式鎖的解決思路,找到所有JVM可以共同訪問的第三方組件,通過第三方組件實作分布式鎖,
6、目前存在的分布式的方案
分布式鎖都是通過第三方組件來實作的,目前比較流行的分布式鎖的解決方案有:
  • 資料庫,通過資料庫可以實作分布式鎖,但是在高并發的情況下對資料庫壓力較大,所以很少使用,
  • Redis,借助Redis也可以實作分布式鎖,而且Redis的Java客戶端種類很多,使用的方法也不盡相同,
  • Zookeeper,Zookeeper也可以實作分布式鎖,同樣Zookeeper也存在多個Java客戶端,使用方法也不相同,

二、電商平臺中針對超賣的解決思路

① 單體架構下針對超賣的解決方案

1、超賣現象一
什么是超賣:某件商品庫存數量10件,結果賣出了15件,

 

 

A和B同時下單,同時讀到庫存,同時減庫存,同時更新資料庫,這是庫存減1,結果卻下單了兩件, 解決辦法: 扣減庫存不在程式中進行,同時通過資料庫解決;向資料庫傳遞庫存增量,扣減1個庫存,增量為-1;在資料庫update陳述句計算庫存,通過update行鎖解決并發問題
2、超賣現象二
使用上述通過資料庫行鎖解決,會出現下面的第二種現象:資料庫的庫存會減為負數,

 

解決方案一 更新庫存成功后,再次檢索商品庫存,如果商品為負數,拋出例外, 解決方案二 添加鎖、將資料庫庫存校驗和資料庫庫存更新系結到一起加上鎖,每次只能有一個得到這個鎖,從而避免庫存被減為負數(-1)的情況,

3、具體代碼實作

  • 創建資料庫表
CREATE DATABASE /*!32312 IF NOT EXISTS*/`distribute` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `distribute`;
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_status` int(1) NOT NULL,
  `receiver_name` varchar(255) NOT NULL,
  `receiver_mobile` varchar(11) NOT NULL,
  `order_amount` decimal(11,0) NOT NULL,
  `create_time` time NOT NULL,
  `create_user` varchar(255) NOT NULL,
  `update_time` time NOT NULL,
  `update_user` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `order_item`;
CREATE TABLE `order_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `purchase_price` decimal(11,0) NOT NULL,
  `purchase_num` int(3) NOT NULL,
  `create_time` time NOT NULL,
  `create_user` varchar(255) NOT NULL,
  `update_time` time NOT NULL,
  `update_user` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `product_name` varchar(255) NOT NULL COMMENT '商品名稱',
  `price` decimal(11,0) NOT NULL COMMENT '價格',
  `count` int(5) NOT NULL COMMENT '庫存',
  `product_desc` varchar(255) NOT NULL COMMENT '描述',
  `create_time` time NOT NULL COMMENT '創建時間',
  `create_user` varchar(255) NOT NULL COMMENT '創建人',
  `update_time` time NOT NULL COMMENT '更新時間',
  `update_user` varchar(255) NOT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100101 DEFAULT CHARSET=utf8mb4;
insert  into `product`(`id`,`product_name`,`price`,`count`,`product_desc`,`create_time`,`create_user`,`update_time`,`update_user`) values (100100,'測驗商品','1',1,'測驗商品','18:06:00','周紅','19:19:21','xxx');
/**后續分布式鎖需要用到**/
DROP TABLE IF EXISTS `distribute_lock`;
CREATE TABLE `distribute_lock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `business_code` varchar(255) NOT NULL COMMENT '根據業務代碼區分,不同業務使用不同鎖',
  `business_name` varchar(255) NOT NULL COMMENT '注釋,標記編碼用途',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
insert  into `distribute_lock`(`id`,`business_code`,`business_name`) values (1,'demo','test');
  • 訂單創建,庫存減1等主要邏輯代碼(這里使用 ReentrantLock 當然,也可以使用其他鎖 )
// 注意:這邊不能使用注解的方式回滾,不然會在事務提交前下一個執行緒會進來
//    @Transactional(rollbackFor = Exception.class)
    public Integer createOrder() throws Exception{
        Product product = null;
        lock.lock();
        try {
            // 開啟事務
            TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition);
            // 查詢到所要購買的商品
            product = productMapper.selectByPrimaryKey(purchaseProductId);
            if (product==null){
                platformTransactionManager.rollback(transaction1);
                throw new Exception("購買商品:"+purchaseProductId+"不存在");
            }
            // 獲取商品當前庫存
            Integer currentCount = product.getCount();
            System.out.println(Thread.currentThread().getName()+"庫存數:"+currentCount);
            // 校驗庫存 (購買商品數量大于庫存數量,拋出例外)
            if (purchaseProductNum > currentCount){
                platformTransactionManager.rollback(transaction1);
                throw new Exception("商品"+purchaseProductId+"僅剩"+currentCount+"件,無法購買");
            }
            productMapper.updateProductCount(purchaseProductNum,"xxx",new Date(),product.getId());
            platformTransactionManager.commit(transaction1);
        }finally {
            lock.unlock();
        }
        // 創建訂單
        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
        Order order = new Order();
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseProductNum)));
        order.setOrderStatus(1);//待處理
        order.setReceiverName("xxx");
        order.setReceiverMobile("15287653421");
        order.setCreateTime(new Date());
        order.setCreateUser("不不不不");
        order.setUpdateTime(new Date());
        order.setUpdateUser("哈哈哈哈");
        orderMapper.insertSelective(order);
        // 創建訂單明細
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProductId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(purchaseProductNum);
        orderItem.setCreateUser("不不不");
        orderItem.setCreateTime(new Date());
        orderItem.setUpdateTime(new Date());
        orderItem.setUpdateUser("哈哈哈哈");
        orderItemMapper.insertSelective(orderItem);
        // 事務提交
        platformTransactionManager.commit(transaction);
        return order.getId();
    }
  • 測驗(使用五個執行緒同時并發的下單)
@Test
public void concurrentOrder() throws InterruptedException {
    Thread.sleep(60000);
    CountDownLatch cdl = new CountDownLatch(5);
    CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    // 創建5個執行緒執行下訂單操作
    ExecutorService es = Executors.newFixedThreadPool(5);
    for (int i =0;i<5;i++){
        es.execute(()->{
            try {
                // 等5個執行緒同時達到 await()時再執行創建訂單服務,這時候5個執行緒會堆積到同一時間執行
                cyclicBarrier.await();
                Integer orderId = orderService.createOrder();
                System.out.println("訂單id:"+orderId);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                // 每個執行緒執行完成之后會減一
                cdl.countDown();
            }
        });
    }
    cdl.await();
    es.shutdown();
}

② 分布式架構下分布式鎖的實作

一、基于資料庫實作分布式鎖

多個行程、多個執行緒訪問共同組件---資料庫 通過select...for update 訪問同一條資料、for update 鎖定資料
  • 在mapper.xml 里面加入如下自定義的SQL
<select id="selectDistributeLock" resultType="com.example.distributelock.model.DistributeLock">
  select * from distribute_lock
  where business_code = #{businessCode,jdbcType=VARCHAR}
  for update
</select>
  • 主要的邏輯實作
@RequestMapping("singleLock")
/**
 * 沒有添加 Transactional 注解前,查詢分布式鎖和sleep二者不是原子操作,在獲取到分布式鎖后自動提交事務,
 * 故不會阻止第二個請求獲取鎖,添加了注解后,在sleep結束前,事務一直未提交,故會等待sleep結束后再行提交事務,
 * 此時第二個請求才能從資料庫中獲取分布式鎖
 */
@Transactional(rollbackFor = Exception.class)
public String singleLock() throws Exception {
    log.info("我進入了方法!");
    DistributeLock distributeLock = distributeLockMapper.selectDistributeLock("demo");
    if (distributeLock==null) throw new Exception("分布式鎖找不到");
    log.info("我進入了鎖!");
    try {
        Thread.sleep(20000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "我已經執行完成!";
}
  • 另一個專案和這個相同,只需要更改埠號即可
優點:
  • 簡單方便,易于理解,易于操作
缺點:
  • 并發量大,對資料庫壓力較大
建議:
  • 作為鎖的資料庫與業務資料庫分開

二、基于Redis的SetNX實作分布式鎖

① 獲取鎖的Redis命令
  • Set resource_name my_random_value NX PX 30000
  • resource_name:資源名稱,可根據不同的業務區分不同的鎖
  • my_random_value:隨機值,每個執行緒的隨機值都不相同,用于釋放鎖時的校驗
  • NX: key不存在是設定成功,key存在則設定不成功
  • PX:自動失效時間,出現例外情況,鎖可以過期失效
② 實作原理
  • 利用NX的原子性,多個執行緒并發時,只有一個執行緒可以設定成功
  • 設定成功即獲得鎖,可以執行后續的業務處理
  • 如果出現例外,過了鎖的有效期,鎖自動釋放,
  • 釋放鎖采用了Redis的delete命令
  • 釋放鎖時校驗值錢設定的亂數,相同才能釋放
  • 釋放鎖的LUA腳本
if redis.call("get",KEYS[1])==argv[1] then 
    return redis.call("del",KEYS[1])
else
    return 0
end
③ 為什么要添加LUA腳本校驗:
沒有校驗可能導致鎖的混亂,如上圖所示:A可能釋放掉了B的鎖,會出現問題,
④ Redis分布式鎖關鍵代碼封裝
package com.example.distributelock.lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@Slf4j
public class RedisLock implements AutoCloseable {
    private RedisTemplate redisTemplate;
    private String key;
    private String value;
    // 過期時間 單位:秒
    private int expireTime;
    public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.expireTime=expireTime;
        this.value =https://www.cnblogs.com/Tom-shushu/p/ UUID.randomUUID().toString();
    }
    /**
     * 獲取分布式鎖
     * @return
     */
    public boolean getLock(){
        RedisCallback<Boolean> redisCallback = connection -> {
            //設定NX
            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
            //設定過期時間
            Expiration expiration = Expiration.seconds(expireTime);
            //序列化key
            byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
            //序列化value
            byte[] redisValue =https://www.cnblogs.com/Tom-shushu/p/ redisTemplate.getValueSerializer().serialize(value);
            //執行setnx操作
            Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
            return result;
        };
        //獲取分布式鎖
        Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
        return lock;
    }
    // 釋放鎖
    public boolean unLock() {
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);
        List<String> keys = Arrays.asList(key);

        Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);
        log.info("釋放鎖的結果:"+result);
        return result;
    }
    @Override
    public void close() throws Exception {
        unLock();
    }
}
⑤ 測驗
@RequestMapping("redisLock")
public String redisLock(){
    log.info("我進入了方法!");
    try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){
        if (redisLock.getLock()) {
            log.info("我進入了鎖!!");
            Thread.sleep(15000);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
    log.info("方法執行完成");
    return "方法執行完成";
}
⑥ 在專案中使用ESJOB定時任務沒問題,但是如果專案中使用了SpringTask來定時,那么在集群中可能會出現任務重復執行的情況,
解決辦法:使用分布式鎖在定時到達后,在執行任務之前,哪個節點獲取到了鎖,哪個節點就來執行這個任務,
⑦ 代碼實作
public class SchedulerService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Scheduled(cron = "0/5 * * * * ?")
    public void sendSms(){
        try(RedisLock redisLock = new RedisLock(redisTemplate,"autoSms",30)) {
            if (redisLock.getLock()){
                log.info("每五秒執行這個程式!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三、基于Zookeeper實作分布式鎖

zookeeper的觀察器
  1. 可設定觀察器的3個方法:getData();getChildren();exists();
  2. 節點資料發生變化,發送給客戶端;
  3. 觀察器只能監控一次,再監控需重新設定;
實作原理

  1. 利用zookeeper的瞬時有序節點的特性;
  2. 多執行緒并發創建瞬時節點時,得到有序的序列;
  3. 序列號最小的執行緒獲得鎖;
  4. 其他執行緒監聽自己序號的前一個序號;
  5. 前一個執行緒執行完成,洗掉自己序號節點;
  6. 下一個序號的執行緒得到通知,繼續執行;
  7. 以此類推,創建節點時,已經確定了執行緒的執行順序;
代碼實作:
package com.example.distributelock.lock;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
@Slf4j
public class ZkLock implements Watcher,AutoCloseable {
    private ZooKeeper zooKeeper;
    private String businessName;
    private String znode;
    public ZkLock(String connectString,String businessName) throws IOException {
        this.zooKeeper = new ZooKeeper(connectString,30000,this);
        this.businessName = businessName;
    }
    public boolean getLock() throws KeeperException, InterruptedException {
        Stat existsNode = zooKeeper.exists("/" + businessName, false);
        if (existsNode == null){
            zooKeeper.create("/" + businessName,businessName.getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        }
        znode = zooKeeper.create("/" + businessName + "/" + businessName + "_", businessName.getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL);
        znode = znode.substring(znode.lastIndexOf("/")+1);
        List<String> childrenNodes = zooKeeper.getChildren("/" + businessName, false);
        Collections.sort(childrenNodes);
        String firstNode = childrenNodes.get(0);
        if (!firstNode.equals(znode)){
            String lastNode = firstNode;
            for (String node:childrenNodes){
                if (!znode.equals(node)){
                    lastNode = node;
                }else {
                    zooKeeper.exists("/"+businessName+"/"+lastNode,true);
                    break;
                }
            }
            synchronized (this){
                wait();
            }
        }
        return true;
    }
    @Override
    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getType() == Event.EventType.NodeDeleted){
            synchronized (this){
                notify();
            }
        }
    }
    @Override
    public void close() throws Exception {
        zooKeeper.delete("/"+businessName+"/"+znode,-1);
        zooKeeper.close();
        log.info("我釋放了鎖");
    }
}
測驗:
@RequestMapping("zkLock")
    public String zkLock(){
        log.info("我進入了方法!");
        try (ZkLock zkLock = new ZkLock("localhost:2181","order")){
            if (zkLock.getLock()) {
                log.info("我進入了鎖!!");
                Thread.sleep(15000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法執行完成");
        return "方法執行完成";
    }
 

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/270933.html

標籤:架構設計

上一篇:醫惠集成平臺調研方案分析(二)

下一篇:互聯網醫院實施方案(一)實施前準備

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more