面向java入門小白的【春節(jié)搶紅包】案例

春節(jié)將至傲茄,又快到了一年一度搶紅包的激動時刻盘榨。

大吉大利

為此呢草巡,我專門針對想要學(xué)習(xí)java山憨,或剛開始學(xué)習(xí)java的小白們郁竟,寫了一段簡單易懂的【春節(jié)搶紅包】代碼棚亩,其中涉及到部分的java編程基礎(chǔ)知識。也涉及到關(guān)于真正搶紅包的思考嘹屯。相信你們一定能有所收貨州弟,同時又能有所聯(lián)想婆翔。

請想要學(xué)習(xí)的同學(xué)們仔閱讀代碼的注釋啃奴,有部分基礎(chǔ)知識的講解我沒有單獨抽取最蕾,放在注釋當中了瘟则。雖然本文不會針對每個知識點講到原理醋拧,但是會在下面列舉出來提綱丹壕,這些是以后工作中常用菌赖,且面試涉及幾率很高的內(nèi)容盏袄,希望各位看完本篇后辕羽,能夠針對這些內(nèi)容加強理解刁愿。

一铣口、基礎(chǔ)知識

內(nèi)部涉及到的基礎(chǔ)知識提綱如下脑题,我會針對每個知識點簡單介紹:

  • 單例模式
    單例模式解決了兩個問題:

    • 保證一個類只有一個實例
    • 為該實例提供一個全局訪問節(jié)點叔遂。

    單例模式包含多種實現(xiàn)方式:比如“餓漢模式”(本文所使用的方式)已艰、“懶漢模式”(線程安全/DCL)哩掺、靜態(tài)內(nèi)部類嚼吞、枚舉等等舱禽。

    本文使用餓漢模式呢蔫,原因在于:線程安全片吊,類加載完成就完成了實例化俏脊,簡單實用爷贫,推薦漫萄。

    想了解更多單例模式的實現(xiàn)請看:http://www.reibang.com/p/6b4d83b71826腾务。

  • AtomicInteger

    除了這個類之外岩瘦,java.util.concurrent.atomic包下都是這一類启昧。這是一個小的類工具包密末,支持在單變量上進行無鎖線程安全編程苏遥。
    通過CAS(compare and swap)提供數(shù)據(jù)的原子更新师抄。

  • CAS(compare and swap叨吮,比較并替換)
    又叫做自旋鎖茶鉴,是以無鎖的方式解決變量原子性問題涵叮。是一種樂觀鎖思想割粮,通過自身的不斷重試舀瓢。

    CAS 的底層是 lock cmpxchg 指令(X86 架構(gòu))京髓,在單核 CPU 和多核 CPU 下都能夠保證【比較-交換】的原子性堰怨。

    • 在多核狀態(tài)下,某個核執(zhí)行到帶 lock 的指令時诬烹,CPU 會讓總線鎖住绞吁,當這個核把此指令執(zhí)行完畢家破,再開啟總線汰聋。這個過程中不會被線程的調(diào)度機制所打斷烹困,保證了多個線程對內(nèi)存操作的準確性拟蜻,是原子的酝锅。

    • 特點:無鎖方式搔扁,線程不會阻塞阁谆。效率高。如果競爭過于激烈嫉入,極大增加重試次數(shù)咒林,效率反而降低。

  • lombok.Data (注解@Data)
    lombok能極大的幫助我們提高代碼的開發(fā)效率欢瞪,其提供了一系列的注解遣鼓,使我們的代碼更為整潔骑祟。

    • @AllArgsConstructor
      生成全參數(shù)的構(gòu)造怯晕,在配合到spring/springboot的構(gòu)造器注入時舟茶,可是不寫其構(gòu)造方法:
    @AllArgsConstructor
    public class ItemStorageAppServiceImpl implements IItemStorageAppService {
    
        private ElasticSearchRepository elastic;
    

    有同學(xué)文為什么不使用@Autowired稚晚?當然可以使用,只是構(gòu)造方法的注入方式是官方推薦的注入方式也搓。

    • @Data
      作用于類上傍妒,是以下注解的集合:@ToString @EqualsAndHashCode @Getter @Setter @RequiredArgsConstructor颤练;也是我比較喜歡使用的注解。

    • @Getter/Setter
      作用于類上宇挫,生成成員變量的get和set方法器瘪。

    關(guān)于其他的橡疼,各位同學(xué)自行學(xué)習(xí)啊衰齐。

  • BigDecimal的常用方式

    BigDecimal是涉及到金額問題的最常用解決方式废酷,所以熟練使用其方法很重要澈蟆,包括加(add)減(subtract)操作趴俘、比較(compareTo)等等。必會類型疲憋,不多說缚柳。

  • LinkedList的特點

    應(yīng)該是早起最常見的面試題了吧,和ArrayList相比有什么不同灰追。同學(xué)們后面自己找相關(guān)資料仔細學(xué)習(xí)下弹澎。

    簡單來說LinkedList是順序的桐猬,鏈表的結(jié)構(gòu),使得其添加/刪除數(shù)據(jù)更快音五,因為不涉及到數(shù)據(jù)遷移的問題;線程不安全坚嗜;查詢數(shù)據(jù)相比于ArrayList要慢苍蔬,需要遍歷查找。

  • 三目運算符的常用方式

    這個沒有什么好說的格仲,常見的代碼編寫方式:

      envelopeLogCache.get(redEnvelopeDO.getId()) == null ? new HashMap<>(4) : envelopeLogCache.get(redEnvelopeDO.getId());
    

    如上的例子表示凯肋,envelopeLogCache根據(jù)edEnvelopeDO.getId()獲取一個Object,這個Object獲取到了嗎苗桂?如果是null,就給它一個new 的HashMap便锨,如果不是空放案,就把這個對象Object返回掸冤。

    自行體會稿湿。

  • 多線程場景下的數(shù)據(jù)異常
    在本文的代碼示例當中,就出現(xiàn)了嚴重的多線程場景下無法保證數(shù)據(jù)的原子性問題涕俗。這一類問題在多線程、高并發(fā)場景是必然會涉及到的枕稀。
    在java當中有對應(yīng)的JUC(java.util.concurrent)類庫去解決這一系列的問題询刹,是java學(xué)習(xí)者必須會的知識。

    可以參考我的文集:http://www.reibang.com/nb/51656252萎坷,當前持續(xù)更新中凹联。

  • java8 lambda表達式
    java8新出現(xiàn)的流式編程方式,極大的縮減代碼復(fù)雜度哆档,使其更加符合面向?qū)ο蟮恼Z義。

    參考文集:http://www.reibang.com/nb/51413235

上面的內(nèi)容很基礎(chǔ)瓜浸,但是都比較重要的澳淑,無論是寫代碼,閱讀源碼插佛,都會涉及杠巡,我只是再次提供一些思路。

二雇寇、編碼開始

從現(xiàn)在開始氢拥,正式進入編碼階段,全部代碼有三個實體類锨侯,兩個實現(xiàn)類嫩海,一個全局變量類,一個初始化處理器類囚痴。另外有兩個是業(yè)務(wù)實現(xiàn)的接口叁怪,但是因為我們用main方法做的此次演示,暫時忽略吧深滚。

2.1 實體類

  • 用戶類
import lombok.Data;

import java.math.BigDecimal;

/**
 * @description: 人
 * @author:weirx
 * @date:2022/1/6 9:40
 * @version:3.0
 */
@Data
public class PeopleDO {

    /**
     * 搶紅包人的id
     */
    private Integer id;

    /**
     * 人名
     */
    private String name;

    /**
     * 金額
     */
    private BigDecimal amount;

    public PeopleDO(Integer id, String name, BigDecimal amount) {
        this.id = id;
        this.name = name;
        this.amount = amount;
    }
}
  • 紅包類
import lombok.Data;

import java.math.BigDecimal;

/**
 * @description: 紅包
 * @author:weirx
 * @date:2022/1/6 9:37
 * @version:3.0
 */
@Data
public class RedEnvelopeDO {

    /**
     * 紅包id
     */
    private Integer id;

    /**
     * 紅包名稱
     */
    private String name;

    /**
     * 金額
     */
    private BigDecimal amount;

    /**
     * 數(shù)量
     */
    private Integer quantity;

    /**
     * 發(fā)紅包人的id
     */
    private Integer peopleId;

    public RedEnvelopeDO(String name, BigDecimal amount, Integer quantity, Integer peopleId) {
        this.name = name;
        this.amount = amount;
        this.quantity = quantity;
        this.peopleId = peopleId;
    }
}
  • 搶紅包歷史記錄類
import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;

/**
 * @description: 搶紅包記錄
 * @author:weirx
 * @date:2022/1/6 9:45
 * @version:3.0
 */
@Data
public class GrabEnvelopeLogDO {

    /**
     * 用戶id
     */
    private Integer peopleId;

    /**
     * 紅包id
     */
    private Integer redEnvelopeId;

    /**
     * 搶到的金額
     */
    private BigDecimal amount;

    /**
     * 發(fā)送時間
     */
    private Date createTime;

    public GrabEnvelopeLogDO(Integer peopleId, Integer redEnvelopeId, BigDecimal amount, Date createTime) {
        this.peopleId = peopleId;
        this.redEnvelopeId = redEnvelopeId;
        this.amount = amount;
        this.createTime = createTime;
    }
}

實體類使用 @Data 注解奕谭,需要引用如下依賴:

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

這個注解使我們在開發(fā)過程中可以極大的增加開發(fā)效率耳璧,我此處使用可以讓我們省去寫get、set方法的繁瑣展箱。調(diào)用時又不耽誤我們正常使用。

另外就是根據(jù)不同的實體類業(yè)務(wù)場景蹬昌,我們可以創(chuàng)建不同參數(shù)列表的構(gòu)造器混驰,這是java“重載”在構(gòu)造器的體現(xiàn)。

2.2 實現(xiàn)類

共有兩個實現(xiàn)類皂贩,分別是發(fā)送紅包的實現(xiàn)類和搶紅包的工具類:

  • 發(fā)送紅包實現(xiàn)類
import com.cloud.bssp.chinesenewyear.grabredenvelope.GlobalDataCache;
import com.cloud.bssp.chinesenewyear.grabredenvelope.GlobalInitProcessor;
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.PeopleDO;
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.RedEnvelopeDO;
import com.cloud.bssp.chinesenewyear.grabredenvelope.service.ISendRedEnvelopeService;

import java.math.BigDecimal;
import java.util.Map;

/**
 * @description: 發(fā)紅包接口
 * @author:weirx
 * @date:2022/1/6 9:56
 * @version:3.0
 */
public class SendRedEnvelopeServiceImpl implements ISendRedEnvelopeService {

    @Override
    public RedEnvelopeDO send(RedEnvelopeDO redEnvelopeDO) throws Exception {
        // 獲取全局變量
        GlobalDataCache globalDataCache = GlobalInitProcessor.getGlobalDataCache();

        //獲取用戶信息
        PeopleDO peopleDO = globalDataCache.getPeopleCache().get(redEnvelopeDO.getPeopleId());
        //校驗用戶金額是否足夠發(fā)紅包
        if (peopleDO.getAmount().compareTo(redEnvelopeDO.getAmount()) < 0) {
            // 直接跑出異常栖榨,調(diào)用時捕獲異常內(nèi)容,實際應(yīng)該定制通用返回結(jié)果明刷,并且有對象的成功失敗標識
            throw new Exception("您當前余額不足婴栽,紅包發(fā)送失敗");
        }
        // 扣除用戶賬戶金額
        BigDecimal subtract = peopleDO.getAmount().subtract(redEnvelopeDO.getAmount());
        peopleDO.setAmount(subtract);
        // 將用戶存入緩存當中
        Map<Integer, PeopleDO> peopleCache = globalDataCache.getPeopleCache();
        peopleCache.put(peopleDO.getId(), peopleDO);
        globalDataCache.setPeopleCache(peopleCache);
        // 獲取全局唯一紅包id,并將紅包存入緩存
        redEnvelopeDO.setId(globalDataCache.getId());
        Map<Integer, RedEnvelopeDO> redEnvelopeCache = globalDataCache.getRedEnvelopeCache();
        redEnvelopeCache.put(redEnvelopeDO.getId(), redEnvelopeDO);
        globalDataCache.setRedEnvelopeCache(redEnvelopeCache);
        return redEnvelopeDO;
    }
}
  • 搶紅包實現(xiàn)類
import cn.hutool.core.util.ObjectUtil;
import com.cloud.bssp.chinesenewyear.grabredenvelope.GlobalDataCache;
import com.cloud.bssp.chinesenewyear.grabredenvelope.GlobalInitProcessor;
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.GrabEnvelopeLogDO;
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.PeopleDO;
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.RedEnvelopeDO;
import com.cloud.bssp.chinesenewyear.grabredenvelope.service.IGrabRedEnvelopeService;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 搶紅包
 * @author:weirx
 * @date:2022/1/6 10:58
 * @version:3.0
 */
public class GrabRedEnvelopeServiceImpl implements IGrabRedEnvelopeService {
    @Override
    public void grab(Integer peopleId, Integer redEnvelopeId) throws Exception {
        // 獲取全局變量
        GlobalDataCache globalDataCache = GlobalInitProcessor.getGlobalDataCache();
        // 獲取用戶
        Map<Integer, PeopleDO> peopleCache = globalDataCache.getPeopleCache();
        PeopleDO peopleDO = peopleCache.get(peopleId);
        // 獲取紅包
        Map<Integer, RedEnvelopeDO> redEnvelopeCache = globalDataCache.getRedEnvelopeCache();
        RedEnvelopeDO redEnvelopeDO = redEnvelopeCache.get(redEnvelopeId);
        // 獲取紅包歷史
        Map<Integer, Map<Integer, GrabEnvelopeLogDO>> envelopeLogCache = globalDataCache.getEnvelopeLogCache();
        Map<Integer, GrabEnvelopeLogDO> integerGrabEnvelopeLogDOMap =
                envelopeLogCache.get(redEnvelopeDO.getId()) == null ? new HashMap<>(4)
                        : envelopeLogCache.get(redEnvelopeDO.getId());
        //判斷紅包是否還有余量
        if (redEnvelopeDO.getQuantity() > 0) {
            // 計算搶到的紅包金額,并減去余額,和可搶數(shù)量
            BigDecimal sub = this.sub(redEnvelopeDO);
            // 用戶增加余額
            peopleDO.setAmount(peopleDO.getAmount().add(sub));
            // 記錄搶紅包歷史
            // 沒有紅包歷史則新建辈末,有則返回不能搶紅包
            if (ObjectUtil.isNotEmpty(integerGrabEnvelopeLogDOMap) &&
                    ObjectUtil.isNotEmpty(integerGrabEnvelopeLogDOMap.get(peopleId))) {
                throw new Exception("很抱歉愚争,您已搶過紅包");
            } else {
                GrabEnvelopeLogDO grabEnvelopeLog = new GrabEnvelopeLogDO(peopleId, redEnvelopeId, sub, new Date());
                integerGrabEnvelopeLogDOMap.put(peopleId, grabEnvelopeLog);
                envelopeLogCache.put(redEnvelopeId, integerGrabEnvelopeLogDOMap);
                globalDataCache.setEnvelopeLogCache(envelopeLogCache);
            }
        } else {
            throw new Exception("很抱歉,紅包已被搶完挤聘!");
        }
    }

    /**
     * description: 計算搶到的紅包金額,并減去余額
     *
     * @param redEnvelopeDO
     * @return: BigDecimal
     * @author: weirx
     * @time: 2022/1/6 11:06
     */
    private BigDecimal sub(RedEnvelopeDO redEnvelopeDO) {
        BigDecimal scale;
        if (redEnvelopeDO.getQuantity() > 1) {
            // 計算能獲取的最金額轰枝,指定最大最小范圍
            int max = redEnvelopeDO.getAmount().intValue();
            double min = 0.01;
            // 隨機范圍,不會超過max组去,也不會小于min
            BigDecimal db = new BigDecimal(Math.random() * (max - min) + min);
            //保留兩位小數(shù)鞍陨,不四舍五入
            scale = db.setScale(2, BigDecimal.ROUND_DOWN);
        } else {
            // 剩一個則獲取全部
            scale = redEnvelopeDO.getAmount();
        }
        //設(shè)置紅包余額
        redEnvelopeDO.setAmount(redEnvelopeDO.getAmount().subtract(scale));
        //設(shè)置剩余可搶數(shù)量
        redEnvelopeDO.setQuantity(redEnvelopeDO.getQuantity() - 1);
        return scale;
    }
}

上面的實現(xiàn)都對應(yīng)實現(xiàn)了其各自的接口,我也提供下从隆,暫時沒有使用:

import org.springframework.stereotype.Service;

/**
 * @description: 搶紅包接口
 * @author:weirx
 * @date:2022/1/6 9:52
 * @version:3.0
 */
@Service
public interface IGrabRedEnvelopeService {

    /**
     * description: 搶紅包接口
     *
     * @param peopleId      用戶id
     * @param redEnvelopeId 紅包id
     * @author: weirx
     * @time: 2022/1/6 9:54
     */
    void grab(Integer peopleId, Integer redEnvelopeId) throws Exception;
}
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.RedEnvelopeDO;
import org.springframework.stereotype.Service;

/**
 * @description: 發(fā)送紅包的接口
 * @author:weirx
 * @date:2022/1/6 9:49
 * @version:3.0
 */
@Service
public interface ISendRedEnvelopeService {

    /**
     * description: 發(fā)送紅包
     *
     * @param redEnvelopeDO 紅包
     * @return: RedEnvelopeDO
     * @author: weirx
     * @time: 2022/1/6 9:50
     */
    RedEnvelopeDO send(RedEnvelopeDO redEnvelopeDO) throws Exception;
}

關(guān)于代碼的詳細解釋都在注釋里面了诚撵,我就不在單獨解釋了。

這里好像使用了hutool工具键闺,一時疏忽寿烟,但是既然用了就給大家提一嘴,需要引入下面的依賴:

<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.18</version>
</dependency>

這是一個大而全的工具類艾杏,只要你想的到的韧衣,基本在這里面都有對應(yīng)的工具類。不要錯過购桑。

2.3 全局變量類及初始化處理器

全局變量類是我走的一個臨時存儲數(shù)據(jù)的類畅铭,本文沒有使用數(shù)據(jù)庫等存儲組件。也正是由于這個原因?qū)е铝硕嗑€程的問題勃蜘,后面會介紹硕噩。

  • 全局變量類
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.GrabEnvelopeLogDO;
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.PeopleDO;
import com.cloud.bssp.chinesenewyear.grabredenvelope.entity.RedEnvelopeDO;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description: 全局數(shù)據(jù)存儲(為了方便學(xué)習(xí),我沒有使用任何的數(shù)據(jù)庫等缭贡,直接通過內(nèi)存當時存儲)
 * @author:weirx
 * @date:2022/1/6 10:01
 * @version:3.0
 */
@Data
public class GlobalDataCache {

    /**
     * AtomicInteger是原子性的炉擅,能夠保證在并發(fā)環(huán)境下的數(shù)據(jù)原子性
     * 通過CAS(compare and swap辉懒,比較并替換)實現(xiàn)的原子性
     *
     * 我使用這個變量作為后面對象的自增id
     */
    private final AtomicInteger atomicInteger = new AtomicInteger(0);

    /**
     * 存儲全部用戶數(shù)據(jù)
     */
    private Map<Integer, PeopleDO> peopleCache = new HashMap<>();

    /**
     * 存儲全部紅包數(shù)據(jù)
     */
    private Map<Integer, RedEnvelopeDO> redEnvelopeCache = new HashMap<>();

    /**
     * 紅包歷史數(shù)據(jù) Map<紅包id, Map<用戶id,GrabEnvelopeLogDO>>
     */
    private Map<Integer, Map<Integer, GrabEnvelopeLogDO>> envelopeLogCache = new HashMap<>();

    /**
     * 獲取全局唯一id
     *
     * incrementAndGet方法使用CAS自加1,保證原子性
     *
     * @return
     */
    public Integer getId() {
        return atomicInteger.incrementAndGet();
    }
}
  • 全局變量初始化處理器
/**
 * @description: 全局初始化處理器
 * @author:weirx
 * @date:2022/1/6 10:12
 * @version:3.0
 */
public class GlobalInitProcessor {

    /**
     * 單例模式(使用靜態(tài)變量實現(xiàn))谍失,個人認為是最好也是最實用的方式眶俩。
     *
     * 靜態(tài)變量在程序運行時就會加載,從而執(zhí)行g(shù)etInstance方法快鱼,創(chuàng)建對象實例颠印,有且僅有一次創(chuàng)建的過程
     *
     * 調(diào)用方使用GlobalInitProcessor.getGlobalDataCache()即可獲取GlobalDataCache的全局唯一實例
     */
    private final static GlobalDataCache GLOBAL_DATA_CACHE = getInstance();

    private static GlobalDataCache getInstance() {
        return new GlobalDataCache();
    }

    public static GlobalDataCache getGlobalDataCache() {
        return GLOBAL_DATA_CACHE;
    }
}

2.4 main方法測試

基礎(chǔ)的代碼都在前面的小節(jié)提供了,本小節(jié)偶爾們主要做些實驗看結(jié)果抹竹。

  • 求和工具類(用于看結(jié)果正確性)
    /**
     * description: 計算總金額
     *
     * @param globalDataCache
     * @return: void
     * @author: weirx
     * @time: 2022/1/6 15:02
     */
    public static void sum(GlobalDataCache globalDataCache) {
        //計算被領(lǐng)取紅包的總金額
        final BigDecimal[] count = {new BigDecimal(0)};
        globalDataCache.getEnvelopeLogCache().forEach((k, v) -> {
            v.forEach((k1, v1) -> {
                count[0] = count[0].add(v1.getAmount());
            });
        });
        System.out.println("被搶總金額:" + count[0]);

        //計算每個人的錢包總金額
        final BigDecimal[] count1 = {new BigDecimal(0)};
        globalDataCache.getPeopleCache().forEach((k, v) -> {
            count1[0] = count1[0].add(v.getAmount());
        });
        System.out.println("所有人的總金額:" + count1[0]);

        //紅包剩余金額
        globalDataCache.getRedEnvelopeCache().forEach((k, v) -> {
            System.out.println("紅包剩余金額:" + v.getAmount());
        });
    }
  • 三人順序搶紅包
    指定張三线罕、李四、王五三個人窃判,其中每個人各自一百塊钞楼;由張三發(fā)送【新年快樂】紅包,總金額100袄琳,共三個人可以搶询件,代碼如下:
    public static void main(String[] args) {
        GlobalDataCache globalDataCache = GlobalInitProcessor.getGlobalDataCache();

        // 準備用戶
        PeopleDO zhangsan = new PeopleDO(globalDataCache.getId(), "張三", new BigDecimal(100));
        PeopleDO lisi = new PeopleDO(globalDataCache.getId(), "李四", new BigDecimal(100));
        PeopleDO wangwu = new PeopleDO(globalDataCache.getId(), "王五", new BigDecimal(100));
        Map<Integer, PeopleDO> map = new HashMap<>(4);
        map.put(zhangsan.getId(), zhangsan);
        map.put(lisi.getId(), lisi);
        map.put(wangwu.getId(), wangwu);
        globalDataCache.setPeopleCache(map);

        //張三發(fā)100的紅包
        SendRedEnvelopeServiceImpl sendRedEnvelopeService = new SendRedEnvelopeServiceImpl();
        RedEnvelopeDO redEnvelopeDO = new RedEnvelopeDO("新年快樂", new BigDecimal(100), 3, zhangsan.getId());
        try {
            sendRedEnvelopeService.send(redEnvelopeDO);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        //張三、李四唆樊、王五按【順序】搶紅包
        LinkedList<Integer> userList = new LinkedList<>();
        userList.add(zhangsan.getId());
        userList.add(lisi.getId());
        userList.add(wangwu.getId());

        GrabRedEnvelopeServiceImpl grabRedEnvelopeService = new GrabRedEnvelopeServiceImpl();
        userList.forEach(i -> {
            try {
                grabRedEnvelopeService.grab(i, redEnvelopeDO.getId());
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        });

        System.out.println(globalDataCache);

        sum(globalDataCache);
    }

代碼簡單雳殊,直接看結(jié)果了:

GlobalDataCache(atomicInteger=4, peopleCache={1=PeopleDO(id=1, name=張三, amount=32.64), 2=PeopleDO(id=2, name=李四, amount=125.06), 3=PeopleDO(id=3, name=王五, amount=142.30)}, redEnvelopeCache={4=RedEnvelopeDO(id=4, name=新年快樂, amount=0.00, quantity=0, peopleId=1)}, envelopeLogCache={4={1=GrabEnvelopeLogDO(peopleId=1, redEnvelopeId=4, amount=32.64, createTime=Thu Jan 06 17:03:21 CST 2022), 2=GrabEnvelopeLogDO(peopleId=2, redEnvelopeId=4, amount=25.06, createTime=Thu Jan 06 17:03:21 CST 2022), 3=GrabEnvelopeLogDO(peopleId=3, redEnvelopeId=4, amount=42.30, createTime=Thu Jan 06 17:03:21 CST 2022)}})
被搶總金額:100.00
所有人的總金額:300.00
紅包剩余金額:0.00
  • 減少紅包數(shù)為兩個
RedEnvelopeDO redEnvelopeDO = new RedEnvelopeDO("新年快樂", new BigDecimal(100), 2, zhangsan.getId());

結(jié)果當中有一個人,王五是搶不到的

很抱歉窗轩,紅包已被搶完夯秃!
GlobalDataCache(atomicInteger=4, peopleCache={1=PeopleDO(id=1, name=張三, amount=34.74), 2=PeopleDO(id=2, name=李四, amount=165.26), 3=PeopleDO(id=3, name=王五, amount=100)}, redEnvelopeCache={4=RedEnvelopeDO(id=4, name=新年快樂, amount=0.00, quantity=0, peopleId=1)}, envelopeLogCache={4={1=GrabEnvelopeLogDO(peopleId=1, redEnvelopeId=4, amount=34.74, createTime=Thu Jan 06 17:06:20 CST 2022), 2=GrabEnvelopeLogDO(peopleId=2, redEnvelopeId=4, amount=65.26, createTime=Thu Jan 06 17:06:20 CST 2022)}})
被搶總金額:100.00
所有人的總金額:300.00
紅包剩余金額:0.00
  • 三個人搶四個紅包
RedEnvelopeDO redEnvelopeDO = new RedEnvelopeDO("新年快樂", new BigDecimal(100), 4, zhangsan.getId());

結(jié)果:

GlobalDataCache(atomicInteger=4, peopleCache={1=PeopleDO(id=1, name=張三, amount=20.24), 2=PeopleDO(id=2, name=李四, amount=173.39), 3=PeopleDO(id=3, name=王五, amount=100.62)}, redEnvelopeCache={4=RedEnvelopeDO(id=4, name=新年快樂, amount=5.75, quantity=1, peopleId=1)}, envelopeLogCache={4={1=GrabEnvelopeLogDO(peopleId=1, redEnvelopeId=4, amount=20.24, createTime=Thu Jan 06 17:08:47 CST 2022), 2=GrabEnvelopeLogDO(peopleId=2, redEnvelopeId=4, amount=73.39, createTime=Thu Jan 06 17:08:47 CST 2022), 3=GrabEnvelopeLogDO(peopleId=3, redEnvelopeId=4, amount=0.62, createTime=Thu Jan 06 17:08:47 CST 2022)}})
被搶總金額:94.25
所有人的總金額:294.25
紅包剩余金額:5.75

通過上面的測試,我們發(fā)現(xiàn)痢艺,三種情況下總金額都是300仓洼,沒有發(fā)生數(shù)據(jù)原子性的問題,那是因為我們的搶紅包是通過一個線程串行去搶的堤舒,然而實際情況是不可能的色建。

大家在年會搶紅包的時候,都是一直盯著手機舌缤,所以人基本同一時刻點擊搶紅包箕戳,先讓不滿足上述的理想環(huán)境。

2.5 并發(fā)場景下的搶紅包

在本章節(jié)我們使用多線程去模擬多人在公司年會搶紅包国撵,仍然是100的紅包陵吸,模擬10個人搶,總共10個紅包介牙,通過10個線程模擬10個人壮虫,修改測試方法如下:

public static void main(String[] args) throws InterruptedException {
        // 定義一個常量,創(chuàng)建的用戶數(shù)环础,也是搶紅包的人數(shù)囚似,同樣是紅包設(shè)定的個數(shù)(此場景就設(shè)置正好的數(shù)量吧)
        int num = 10;

        //獲取全局變量
        GlobalDataCache globalDataCache = GlobalInitProcessor.getGlobalDataCache();

        // 準備用戶, Map初始化記得賦予初始大小剩拢,設(shè)置值 * 負載因子(0.75) = 實際容量
        Map<Integer, PeopleDO> peopleDOMap = new HashMap<>(128);
        for (int i = 0; i < num; i++) {
            //循環(huán)初始化用戶,添加到peopleDOMap中
            PeopleDO peopleDO = new PeopleDO(globalDataCache.getId(), "用戶:" + i, new BigDecimal(100));
            peopleDOMap.put(peopleDO.getId(), peopleDO);
        }
        // 設(shè)置用戶到全局變量
        globalDataCache.setPeopleCache(peopleDOMap);

        //實例化發(fā)紅包的業(yè)務(wù)實現(xiàn)
        SendRedEnvelopeServiceImpl sendRedEnvelopeService = new SendRedEnvelopeServiceImpl();
        //構(gòu)造一個紅包
        RedEnvelopeDO redEnvelopeDO = new RedEnvelopeDO(
                "新年快樂", new BigDecimal(100), num, peopleDOMap.keySet().iterator().next());
        try {
            // 發(fā)送紅包
            sendRedEnvelopeService.send(redEnvelopeDO);
        } catch (Exception e) {
            //此處會捕獲手動拋出的“用戶余額不足異橙幕剑”
            System.out.println(e.getMessage());
        }

        // 獲取錢紅包實現(xiàn)
        GrabRedEnvelopeServiceImpl grabRedEnvelopeService = new GrabRedEnvelopeServiceImpl();

        // 使用屏障或者叫同步器徐伐,指定一個數(shù)字,當線程調(diào)用一個await方法募狂,數(shù)字加1呵晨,知道等于設(shè)置的數(shù)值,所有線程才會開始執(zhí)行熬尺,否則一直處于阻塞狀態(tài)。
        CyclicBarrier cyclicBarrier = new CyclicBarrier(num);
        // 并發(fā)的搶紅包
        peopleDOMap.forEach((k, v) -> {
            // 創(chuàng)建和人數(shù)一樣多的線程
            new Thread(() -> {
                try {
                    // 等到所有線程到達
                    cyclicBarrier.await();
                    // 執(zhí)行搶紅包方法
                    grabRedEnvelopeService.grab(k, redEnvelopeDO.getId());
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            }).start();
        });
        // 主線程休眠1秒谓罗,否則業(yè)務(wù)線程還沒執(zhí)行完粱哼,主線程就結(jié)束了,看不到結(jié)果
        TimeUnit.SECONDS.sleep(1);
        System.out.println(globalDataCache);

        // 結(jié)果統(tǒng)計
        sum(globalDataCache);
    }

結(jié)果:

GlobalDataCache(atomicInteger=11, peopleCache={1=PeopleDO(id=1, name=用戶:0, amount=60.07), 2=PeopleDO(id=2, name=用戶:1, amount=106.39), 3=PeopleDO(id=3, name=用戶:2, amount=125.67), 4=PeopleDO(id=4, name=用戶:3, amount=186.65), 5=PeopleDO(id=5, name=用戶:4, amount=178.62), 6=PeopleDO(id=6, name=用戶:5, amount=180.41), 7=PeopleDO(id=7, name=用戶:6, amount=174.70), 8=PeopleDO(id=8, name=用戶:7, amount=170.05), 9=PeopleDO(id=9, name=用戶:8, amount=170.19), 10=PeopleDO(id=10, name=用戶:9, amount=126.10)}, redEnvelopeCache={11=RedEnvelopeDO(id=11, name=新年快樂, amount=-317.50, quantity=2, peopleId=1)}, envelopeLogCache={11={3=GrabEnvelopeLogDO(peopleId=3, redEnvelopeId=11, amount=25.67, createTime=Thu Jan 06 17:17:44 CST 2022)}})
被搶總金額:25.67
所有人的總金額:1478.85
紅包剩余金額:-317.50

上述結(jié)果大家看到了吧檩咱,這才是10個人揭措,看到數(shù)額完全不對了,紅包剩余都變成了負數(shù)的刻蚯,總金額頁遠超了1000塊绊含,二紅包被搶的總共在25塊多。要是這樣發(fā)紅包的老板要賠死都不知道咋回事啊炊汹。

  • 問題分析
    我們的數(shù)據(jù)都是存儲在一個共享變量GlobalDataCache當中的躬充,我們首先結(jié)合下圖JMM(java內(nèi)存模型)分析下:
JMM.png

我們共享變量GlobalDataCache在實例化后存儲在堆中,堆是共享的讨便;當線程被創(chuàng)建充甚,并且使用到這個GlobalDataCache時,會從堆中獲取霸褒,然后將其存儲到自己的虛擬機棧當中伴找;虛擬機棧中有棧幀,每個方法就是一個棧幀废菱,棧幀中又包含本地變量表技矮,此時的GlobalDataCache就存在這里面。所以當線程咋方法中修改GlobalDataCache時殊轴,修改的只是本地變量表的數(shù)據(jù)衰倦,沒有修改隊中的數(shù)據(jù),只有當當方法全部完成后旁理,才會同步到隊中的GlobalDataCache耿币。此時必然產(chǎn)生數(shù)據(jù)不同步的問題了。

  • 解決方案
    優(yōu)點基礎(chǔ)的同學(xué)一定會想到使用鎖實現(xiàn)韧拒,我們在java中常間的鎖有Synchronized淹接,LockSupport以及ReetrantLock十性。

    想了解原理的同學(xué)可以看我的這個專題:http://www.reibang.com/u/e62c72db32f1

    本文使用ReetrantLock來解決問題,所以有修改后的測試方法如下:

    public static void main(String[] args) throws InterruptedException {
        // 定義一個常量塑悼,創(chuàng)建的用戶數(shù)劲适,也是搶紅包的人數(shù),同樣是紅包設(shè)定的個數(shù)(為了測試紅包不足厢蒜,實際會減少)
        int num = 10;

        //獲取全局變量
        GlobalDataCache globalDataCache = GlobalInitProcessor.getGlobalDataCache();

        // 準備用戶, Map初始化記得賦予初始大小霞势,設(shè)置值 * 負載因子(0.75) = 實際容量
        Map<Integer, PeopleDO> peopleDOMap = new HashMap<>(128);
        for (int i = 0; i < num; i++) {
            //循環(huán)初始化用戶,添加到peopleDOMap中
            PeopleDO peopleDO = new PeopleDO(globalDataCache.getId(), "用戶:" + i, new BigDecimal(100));
            peopleDOMap.put(peopleDO.getId(), peopleDO);
        }
        // 設(shè)置用戶到全局變量
        globalDataCache.setPeopleCache(peopleDOMap);

        //實例化發(fā)紅包的業(yè)務(wù)實現(xiàn)
        SendRedEnvelopeServiceImpl sendRedEnvelopeService = new SendRedEnvelopeServiceImpl();
        //構(gòu)造一個紅包
        RedEnvelopeDO redEnvelopeDO = new RedEnvelopeDO(
                "新年快樂", new BigDecimal(100), num - 2, peopleDOMap.keySet().iterator().next());
        try {
            // 發(fā)送紅包
            sendRedEnvelopeService.send(redEnvelopeDO);
        } catch (Exception e) {
            //此處會捕獲手動拋出的“用戶余額不足異嘲哐唬”
            System.out.println(e.getMessage());
        }

        // 獲取錢紅包實現(xiàn)
        GrabRedEnvelopeServiceImpl grabRedEnvelopeService = new GrabRedEnvelopeServiceImpl();

        // 使用屏障或者叫同步器愕贡,指定一個數(shù)字,當線程調(diào)用一個await方法巷屿,數(shù)字加1固以,知道等于設(shè)置的數(shù)值,所有線程才會開始執(zhí)行嘱巾,否則一直處于阻塞狀態(tài)憨琳。
        CyclicBarrier cyclicBarrier = new CyclicBarrier(num);
        // 保證數(shù)據(jù)原子性,線程同步旬昭,等待線程處于阻塞狀態(tài)篙螟,lock/unlock
        ReentrantLock lock = new ReentrantLock();
        peopleDOMap.forEach((k, v) -> {
            // 創(chuàng)建和人數(shù)一樣多的線程
            new Thread(() -> {
                try {
                    // 等到所有線程到達
                    cyclicBarrier.await();
                    // 鎖住搶紅包方法grap,此時是互斥的问拘,只有當前線程能進來遍略,其余縣城在阻塞隊列等待
                    lock.lock();
                    // 執(zhí)行搶紅包方法
                    grabRedEnvelopeService.grab(k, redEnvelopeDO.getId());
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                } finally {
                    // 釋放互斥鎖,要保證在finally當中執(zhí)行釋放鎖骤坐,防止死鎖發(fā)生
                    lock.unlock();
                }
            }).start();
        });
        // 主線程休眠1秒墅冷,否則業(yè)務(wù)線程還沒執(zhí)行完,主線程就結(jié)束了或油,看不到結(jié)果
        TimeUnit.SECONDS.sleep(1);
        System.out.println(globalDataCache);

        // 結(jié)果統(tǒng)計
        sum(globalDataCache);
    }

此次我們還減少兩個紅包數(shù)量寞忿,即8個:

 RedEnvelopeDO redEnvelopeDO = new RedEnvelopeDO("新年快樂", new BigDecimal(100), num - 2, peopleDOMap.keySet().iterator().next());

結(jié)果:

很抱歉,紅包已被搶完顶岸!
很抱歉腔彰,紅包已被搶完!
GlobalDataCache(atomicInteger=11, peopleCache={1=PeopleDO(id=1, name=用戶:0, amount=4.29), 2=PeopleDO(id=2, name=用戶:1, amount=100.69), 3=PeopleDO(id=3, name=用戶:2, amount=102.21), 4=PeopleDO(id=4, name=用戶:3, amount=101.59), 5=PeopleDO(id=5, name=用戶:4, amount=100.00), 6=PeopleDO(id=6, name=用戶:5, amount=100), 7=PeopleDO(id=7, name=用戶:6, amount=100.00), 8=PeopleDO(id=8, name=用戶:7, amount=100.85), 9=PeopleDO(id=9, name=用戶:8, amount=100), 10=PeopleDO(id=10, name=用戶:9, amount=190.37)}, redEnvelopeCache={11=RedEnvelopeDO(id=11, name=新年快樂, amount=0.00, quantity=0, peopleId=1)}, envelopeLogCache={11={1=GrabEnvelopeLogDO(peopleId=1, redEnvelopeId=11, amount=4.29, createTime=Thu Jan 06 17:41:25 CST 2022), 2=GrabEnvelopeLogDO(peopleId=2, redEnvelopeId=11, amount=0.69, createTime=Thu Jan 06 17:41:25 CST 2022), 3=GrabEnvelopeLogDO(peopleId=3, redEnvelopeId=11, amount=2.21, createTime=Thu Jan 06 17:41:25 CST 2022), 4=GrabEnvelopeLogDO(peopleId=4, redEnvelopeId=11, amount=1.59, createTime=Thu Jan 06 17:41:25 CST 2022), 5=GrabEnvelopeLogDO(peopleId=5, redEnvelopeId=11, amount=0.00, createTime=Thu Jan 06 17:41:25 CST 2022), 7=GrabEnvelopeLogDO(peopleId=7, redEnvelopeId=11, amount=0.00, createTime=Thu Jan 06 17:41:25 CST 2022), 8=GrabEnvelopeLogDO(peopleId=8, redEnvelopeId=11, amount=0.85, createTime=Thu Jan 06 17:41:25 CST 2022), 10=GrabEnvelopeLogDO(peopleId=10, redEnvelopeId=11, amount=90.37, createTime=Thu Jan 06 17:41:25 CST 2022)}})
被搶總金額:100.00
所有人的總金額:1000.00
紅包剩余金額:0.00

由上所示發(fā)現(xiàn)沒有任何問題辖佣。

三霹抛、微服務(wù)常用組件

前面扯了一大堆基礎(chǔ)到不能在基礎(chǔ)的內(nèi)容,下面我們自由飛翔一下卷谈,看看當今企業(yè)中常見的技術(shù)棧有哪些杯拐,可以利用到我們的搶紅包的場景當中。

下面我簡單畫一幅架構(gòu)圖,列出比較常用的架構(gòu)設(shè)計:

架構(gòu).png

如上圖所示端逼,從web請求開始朗兵,涉及到如下組建,咱們逐一舉例:

  • 負載均衡
    磁層主要是對網(wǎng)關(guān)做負載均衡顶滩,企業(yè)通常采用軟負載余掖,常用nginx等,或使用F5的負載均衡組件礁鲁。

  • 網(wǎng)關(guān)層
    網(wǎng)關(guān)在如今的主流java開發(fā)領(lǐng)域盐欺,使用較多的是springCloud生態(tài)的zuul或者gateway組件,自帶負載均衡和代理轉(zhuǎn)發(fā)的功能仅醇。同時可以作為權(quán)限驗證的組件冗美,如集成JWT等。也可以做請求攔截析二,白名單等粉洼。

  • 業(yè)務(wù)服務(wù)層
    通常就是咱們寫業(yè)務(wù)代碼的一層,目前主流的框架有兩個甲抖,分別是阿里開源的dubbo生態(tài),隨著注冊中心nacos的出現(xiàn)心铃,可以取代原本的zookeeper准谚,目前使用量較廣。另一個就是springCloud的生態(tài)去扣,提供豐富的組件庫柱衔,F(xiàn)eign,ribbon愉棱,eureka,hystrix等等組件,應(yīng)當是目前使用量最廣泛的java微服務(wù)框架存淫。

  • 數(shù)據(jù)持久層
    此層我應(yīng)該再細分為三個層面:

    • 關(guān)系型數(shù)據(jù)庫:主流的是mysql和PostgreSQL谜喊,傳統(tǒng)行業(yè)可能還在使用Oracle;以及目前阿里背書的OceanBase等朋其。
    • 緩存:隨著目前服務(wù)數(shù)量王浴,用戶量的增加,緩存對于互聯(lián)網(wǎng)應(yīng)用來說越來越重要梅猿。主流是redis和MongoDB氓辣,使用量都很廣泛。
    • 搜索引擎:主流是Elasticsearch袱蚓、solr等钞啸。業(yè)務(wù)場景對于查詢量大的,修改少的場景也可以使用。
  • 限流体斩、熔斷梭稚、降級
    當并發(fā)量很大,系統(tǒng)不足以應(yīng)付的時候硕勿,可以使用這些策略保證系統(tǒng)的可用性哨毁。springcloud提供自帶的組件hystrix。
    但是我此處指的是作用與網(wǎng)關(guān)層源武,也就是請求的入口處扼褪。推薦使用阿里開源的Sentinel。

  • 注冊中心/配置中心
    目前主流的注冊中心在國內(nèi)可以說就是阿里巴巴開源的nacos了粱栖,集服務(wù)發(fā)現(xiàn)和動態(tài)配置于一身话浇,同時支持dubbo和springcloud等主流的微服務(wù)框架。
    另外springcloud自帶的eureka暫時不推薦使用了闹究,在易用性上來說完全不如nacos幔崖。還需要單獨選擇一套配置中心搭配,可以使用zookeeper渣淤,etcd等組件自行開發(fā)赏寇;也可以使用攜程的Apollo,也是不錯的配置中心組件价认。

  • 消息中間件
    消息中間件是目前互聯(lián)網(wǎng)中的明星了嗅定,在解決高并發(fā)量,大吞吐量用踩,異步解耦等方面可以說做到了極致渠退。有其適合大量訂單等場景。
    常用的有阿里巴巴的RocketMQ脐彩,RabbitMQ以及經(jīng)久不衰的Kafka碎乃。

  • EFK/ELK
    日志收集組件。對于一些大型傳統(tǒng)行業(yè)惠奸,涉及到一些審計的工作梅誓,他們對于操作日志的記錄非常嚴格,此時需要一套專門的系統(tǒng)來做佛南。
    主流的有elasticsearch+ fluentd + kibana 和大多數(shù)在企業(yè)使用的elasticsearch+ Logstash + kibana证九。日志存儲組件都是Elasticsearch,查詢組件都是Kibana共虑,主要差別在于日志的收集組件愧怜,這將影響到日志記錄的整體速度,除了上面兩種還有不少的選擇如:flume妈拌、filebeat等

  • 鏈路追蹤
    這個屬于保證系統(tǒng)可用性的組件拥坛,可以反映當前系統(tǒng)的運行狀態(tài)蓬蝶,以及各服務(wù)之間額調(diào)用關(guān)系,甚至于網(wǎng)絡(luò)請求的吞吐量等猜惋⊥璺眨可以集成郵件等組件實現(xiàn)異常告警推送。

    常用的我推薦skywalking著摔,同時還有zipkin缓窜,和新興的jeager。

  • Prometheus + Grafana
    這是一套云原生的監(jiān)控系統(tǒng)谍咆,可以監(jiān)控服務(wù)器禾锤,服務(wù),以及各種會用到的組件摹察,使用exporter將數(shù)據(jù)收集到prometheus進行存儲恩掷。之后由Grafana提供動態(tài)可配置的報表進行定制展示,是目前最好的監(jiān)控組件供嚎。

關(guān)于常用的組件就介紹到這吧黄娘,當然還有很多很多沒有提到,也還有很多我沒有用過克滴,希望在工作中不斷地學(xué)習(xí)吧逼争。

四、總結(jié)

一篇java入門小知識劝赔,不知道對朋友們有沒有幫助誓焦,碼字不易,給點個贊和關(guān)注啊望忆。

學(xué)完本篇搶紅包的代碼罩阵,保準讓你過年每個紅包都不落下竿秆,每次紅包都搶最大的F羯恪!

小弟提前給你們拜年了S母帧G副浮!

大吉大利
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匪燕,一起剝皮案震驚了整個濱河市蕾羊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帽驯,老刑警劉巖龟再,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尼变,居然都是意外死亡利凑,警方通過查閱死者的電腦和手機浆劲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哀澈,“玉大人牌借,你說我怎么就攤上這事「畎矗” “怎么了膨报?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長适荣。 經(jīng)常有香客問我现柠,道長,這世上最難降的妖魔是什么束凑? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任晒旅,我火速辦了婚禮,結(jié)果婚禮上汪诉,老公的妹妹穿的比我還像新娘废恋。我一直安慰自己,他們只是感情好扒寄,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布鱼鼓。 她就那樣靜靜地躺著,像睡著了一般该编。 火紅的嫁衣襯著肌膚如雪迄本。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天课竣,我揣著相機與錄音嘉赎,去河邊找鬼。 笑死于樟,一個胖子當著我的面吹牛公条,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迂曲,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼靶橱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了路捧?” 一聲冷哼從身側(cè)響起关霸,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杰扫,沒想到半個月后队寇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡章姓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年佳遣,在試婚紗的時候發(fā)現(xiàn)自己被綠了炭序。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡苍日,死狀恐怖惭聂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情相恃,我是刑警寧澤辜纲,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站拦耐,受9級特大地震影響耕腾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杀糯,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一扫俺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧固翰,春花似錦狼纬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至歉铝,卻和暖如春盈简,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背太示。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工柠贤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人类缤。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓臼勉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呀非。 傳聞我的和親對象是個殘疾皇子坚俗,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容