面試官:你是如何使用JDK來實現自己的快取(支援高併發)?
需求分析
專案中經常會遇到這種場景:一份資料需要在多處共享,有些資料還有時效性,過期自動失效。比如手機驗證碼,傳送之後需要快取起來,然後處於安全性考慮,一般還要設定有效期,到期自動失效。我們怎麼實現這樣的功能呢?
解決方案
使用現有的快取技術框架,比如redis,ehcache。優點:成熟,穩定,功能強大;缺點,專案需要引入對應的框架,不夠輕量。
如果不考慮分散式,只是在單執行緒或者多執行緒間作資料快取,其實完全可以自己手寫一個快取工具。下面就來簡單實現一個這樣的工具。
先上程式碼:
import java.util.HashMap; import java.util.Map; import java.util.concurrent.*; /** * @Author: lixk * @Date: 2018/5/9 15:03 * @Description: 簡單的記憶體快取工具類 */ public class Cache { //鍵值對集合 private final static Map<String, Entity> map = new HashMap<>(); //定時器執行緒池,用於清除過期快取 private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); /** * 新增快取 * * @param key 鍵 * @param data 值 */ public synchronized static void put(String key, Object data) { Cache.put(key, data, 0); } /** * 新增快取 * * @param key 鍵 * @param data 值 * @param expire 過期時間,單位:毫秒, 0表示無限長 */ public synchronized static void put(String key, Object data, long expire) { //清除原鍵值對 Cache.remove(key); //設定過期時間 if (expire > 0) { Future future = executor.schedule(new Runnable() { @Override public void run() { //過期後清除該鍵值對 synchronized (Cache.class) { map.remove(key); } } }, expire, TimeUnit.MILLISECONDS); map.put(key, new Entity(data, future)); } else { //不設定過期時間 map.put(key, new Entity(data, null)); } } /** * 讀取快取 * * @param key 鍵 * @return */ public synchronized static Object get(String key) { Entity entity = map.get(key); return entity == null ? null : entity.getValue(); } /** * 讀取快取 * * @param key 鍵 * * @param clazz 值型別 * @return */ public synchronized static <T> T get(String key, Class<T> clazz) { return clazz.cast(Cache.get(key)); } /** * 清除快取 * * @param key * @return */ public synchronized static Object remove(String key) { //清除原快取資料 Entity entity = map.remove(key); if (entity == null) return null; //清除原鍵值對定時器 Future future = entity.getFuture(); if (future != null) future.cancel(true); return entity.getValue(); } /** * 查詢當前快取的鍵值對數量 * * @return */ public synchronized static int size() { return map.size(); } /** * 快取實體類 */ private static class Entity { //鍵值對的value private Object value; //定時器Future private Future future; public Entity(Object value, Future future) { this.value = value; this.future = future; } /** * 獲取值 * * @return */ public Object getValue() { return value; } /** * 獲取Future物件 * * @return */ public Future getFuture() { return future; } } }
本工具類主要採用 HashMap+定時器執行緒池 實現,map 用於儲存鍵值對資料,map的value是 Cache 的內部類物件 Entity,Entity 包含 value 和該鍵值對的生命週期定時器 Future。Cache 類對外只提供了 put(key, value), put(key, value, expire), get(key), get(key, class), remove(key), size()幾個同步方法。
當新增鍵值對資料的時候,首先會呼叫remove()方法,清除掉原來相同 key 的資料,並取消對應的定時清除任務,然後新增新資料到 map 中,並且,如果設定了有效時間,則新增對應的定時清除任務到定時器執行緒池。
測試
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * @Author: lixk * @Date: 2018/5/9 16:40 * @Description: 快取工具類測試 */ public class CacheTest { /** * 測試 * * @param args */ public static void main(String[] args) throws InterruptedException, ExecutionException { String key = "id"; //不設定過期時間 System.out.println("***********不設定過期時間**********"); Cache.put(key, 123); System.out.println("key:" + key + ", value:" + Cache.get(key)); System.out.println("key:" + key + ", value:" + Cache.remove(key)); System.out.println("key:" + key + ", value:" + Cache.get(key)); //設定過期時間 System.out.println("\n***********設定過期時間**********"); Cache.put(key, "123456", 1000); System.out.println("key:" + key + ", value:" + Cache.get(key)); Thread.sleep(2000); System.out.println("key:" + key + ", value:" + Cache.get(key)); /******************併發效能測試************/ System.out.println("\n***********併發效能測試************"); //建立有10個執行緒的執行緒池,將1000000次操作分10次新增到執行緒池 ExecutorService executorService = Executors.newFixedThreadPool(10); Future[] futures = new Future[10]; /********新增********/ { long start = System.currentTimeMillis(); for (int j = 0; j < 10; j++) { futures[j] = executorService.submit(() -> { for (int i = 0; i < 100000; i++) { Cache.put(Thread.currentThread().getId() + key + i, i, 300000); } }); } //等待全部執行緒執行完成,列印執行時間 for (Future future : futures) { future.get(); } System.out.printf("新增耗時:%dms\n", System.currentTimeMillis() - start); } /********查詢********/ { long start = System.currentTimeMillis(); for (int j = 0; j < 10; j++) { futures[j] = executorService.submit(() -> { for (int i = 0; i < 100000; i++) { Cache.get(Thread.currentThread().getId() + key + i); } }); } //等待全部執行緒執行完成,列印執行時間 for (Future future : futures) { future.get(); } System.out.printf("查詢耗時:%dms\n", System.currentTimeMillis() - start); } System.out.println("當前快取容量:" + Cache.size()); } }
測試結果:
***********不設定過期時間**********
key:id, value:123
key:id, value:123
key:id, value:null
***********設定過期時間**********
key:id, value:123456
key:id, value:null
***********併發效能測試************
新增耗時:2313ms
查詢耗時:335ms
當前快取容量:1000000
測試程式使用有10個執行緒的執行緒池來模擬併發,總共執行一百萬次新增和查詢操作,時間大約都在兩秒多,`表現還不錯,每秒40萬讀寫併發應該還是可以滿足大多數高併發場景的^_^
寫在最後
- 第一:看完點贊,感謝您的認可;
- ...
- 第二:隨手轉發,分享知識,讓更多人學習到;
- ...
- 第三:記得點關注,每天更新的!!!
- ...
相關推薦
面試官:你是如何使用JDK來實現自己的快取(支援高併發)?
需求分析 專案中經常會遇到這種場景:一份資料需要在多處共享,有些資料還有時效性,過期自動失效。比如手機驗證碼,傳送之後需要快取起來,然後處於安全性考慮,一般還要設定有效期,到期自動失效。我們怎麼實現這樣的功能呢? 解決方案 使用現有的快取技術框架,比如redis,ehcache。優點:成熟,穩定,功能強大;
面試官:你看過Redis資料結構底層實現嗎?
面試中,redis也是很受面試官親睞的一部分。我向在這裡講的是redis的底層資料結構,而不是你理解的五大資料結構。你有沒有想過redis底層是怎樣的資料結構呢,他們和我們java中的HashMap、List、等使用的資料結構有什麼區別呢。 1. 字串處理(string) 我們都知道redis是用C語言寫
面試官:你瞭解過Redis物件底層實現嗎
上一章我們講了Redis的底層資料結構,不瞭解的人可能會有疑問:這個和平時用的五大物件有啥關係呢?這一章我們就主要解釋他們所建立的聯絡。 看這個文章之前,如果對ziplist、skiplist、intset等資料結構不熟悉的話,建議先回顧一下上一章節:面試官:你看過Redis資料結構底層實現嗎? 0. 五
面試官:你說你精通 Docker,那你來詳細說說 Dockerfile 吧
接上一篇:30分鐘快速上手Docker,看這篇就對了! 一、 帶著問題學Dockerfile 1、疑問 我們都知道從遠端倉庫可以pull一個tomcat等映象下來,然後docker run啟動容器,然後docker exec -it 容器id /bin/bash進入容器,往webapps下仍我們的程式。等等這
怎麼回答面試官:你對Spring的理解
spring呢,是pivotal公司維護的一系列開源工具的總稱,最為人所知的是spring mvc,事實上,他們都是基於spring framework,並且再其上繼續增強,為某一方面服務的java元件。最近spring framework 剛升級到5,非常不錯。比較常見的有
面試官:你為什麼要離開之前的公司?
離職、跳槽是一件很正常的事。但是難免在面試時會被問到:“你為什麼要離開之前的公司?”首先,你要知道面試官這麼問的目的是什麼?面試官問這個問題,是想知道你離開之前公司的原因是否合理,是想知道你是否對公司忠誠、有熱情、感興趣,想考察你動機是否單純、是否只是把公司當成一個“避難所”
面試官:你分析過SpringMVC的源碼嗎?
技術分享 rop 調用 直接 setview gate rem code 上傳 1. MVC使用 在研究源碼之前,先來回顧以下springmvc 是如何配置的,這將能使我們更容易理解源碼。 1.1 web.xml <servlet> <servle
【BAT面試題系列】面試官:你了解樂觀鎖和悲觀鎖嗎?
次數 catch val util overflow info 基本概念 因此 問題 前言 樂觀鎖和悲觀鎖問題,是出現頻率比較高的面試題。本文將由淺入深,逐步介紹它們的基本概念、實現方式(含實例)、適用場景,以及可能遇到的面試官追問,希望能夠幫助你打動面試官。 目錄
面試官:你有m個雞蛋,如何用最少的次數測出雞蛋會在哪一層碎?
假設你面前有一棟n層的大樓和m個雞蛋,假設將雞蛋從f層或更高的地方放扔下去,雞蛋才會碎,否則就不會。你需要設計一種策略來確定f的值,求最壞情況下扔雞蛋次數的最小值。 leetcode原題連結 乍一看這道題很抽象,可能有的人一看到這個題目從來沒做過,就懵逼了。其實不用慌張,再花裡胡哨的題目,最後都可以抽象成我們
面試官:你連RESTful都不知道我怎麼敢要你?
目錄 01 前言 02 RESTful的來源 03 RESTful6大原則 1. C-S架構 2. 無狀態 3.統一的介面 4.一致的資料格式
面試系列-面試官:你能給我解釋一下javascript中的this嗎?
一.前言 關於javascript中的this物件,可能已經被大家說爛了。 即使是這樣,我依然決定將這篇文章給水出來。畢竟全國在新型肺炎的影響下,公司沒法正常復工。 除了刷刷手機,還是要適當的學習一下。 廢柴是真不好當,勞逸結合才是王道。 二.正戲開始 面試官:你能給我解釋一下javasc
【原創】面試官:你回去等通知吧!
這是why技術的第37篇原創文章 老規矩,先聊聊生活,上面這張圖片是我週一拍的。 週一晚上下班後發現公司樓下推著三輪車賣花的阿姨又開始買花了。整個路口只有她一個人在做生意,整條路上也沒有幾個人,大家都低著頭匆匆走著,繁花中帶著點憂傷。 於是,我去買了一把白玫瑰。 上週日把《霍亂時期的愛情》看完了,就剛好當
Java堆記憶體是執行緒共享的!面試官:你確定嗎?
Java作為一種面向物件的,跨平臺語言,其物件、記憶體等一直是比較難的知識點,所以,即使是一個Java的初學者,也一定或多或少的對JVM有一些瞭解。可以說,關於JVM的相關知識,基本是每個Java開發者必學的知識點,也是面試的時候必考的知識點。 在JVM的記憶體結構中,比較常見的兩個區域就是堆記憶體和棧記憶
面試官:你知道哪些事務失效的場景?
前言 宣告式事務是Spring功能中最爽之一,可是有些時候,我們在使用宣告式事務並未生效,這是為什麼呢? 文章首發於微信公眾號【碼猿技術專欄】 今天陳某帶大家來聊一聊宣告事務的幾種失效場景。本文將會從以下兩個方面來說一下事務為什麼會失效? @Transactional介紹 @Transactional失效場
面試官:你剛說你喜歡研究新技術,那麼請說說你對 Blazor 的瞭解
閱讀本文大概需要 1.5 分鐘。 最近在幾個微信 .NET 交流群裡大家討論比較頻繁的話題就是這幾天自己的面試經歷。 面試官:“你剛說你喜歡研究新技術,那麼你對 Blazor 瞭解多少?”。 作為一位專注於 .NET 開發的軟體工程師,你好意思說你對 Blazor 一點也不解嗎?.NET 新技術也就是那
面試官:你經歷過資料庫遷移麼?有哪些注意點和難點?
前言 最近寫了很多資料庫相關的文章,大家基本上對資料庫也有了很多的瞭解,資料庫本身有所瞭解了,我們是不是應該回歸業務本身呢? 大家去了解過自己企業資料庫的部署方式麼?是怎麼部署的,又是部署在哪裡的?部署過程中可能會出現的問題有哪些? 是主從?還是雙主?有沒有分庫?大的表做了分表沒?等等...部署方式大概率也都
面試官:你說說互斥鎖、自旋鎖、讀寫鎖、悲觀鎖、樂觀鎖的應用場景
前言 生活中用到的鎖,用途都比較簡單粗暴,上鎖基本是為了防止外人進來、電動車被偷等等。 但生活中也不是沒有 BUG 的,比如加鎖的電動車在「廣西 - 竊·格瓦拉」面前,鎖就是形同虛設,只要他願意,他就可以輕輕鬆鬆地把你電動車給「順走」,不然打工怎麼會是他這輩子不可能的事情呢?牛逼之人,必有牛
面試官:你真的瞭解Redis分散式鎖嗎?
![](https://img2020.cnblogs.com/blog/1478697/202101/1478697-20210113230123649-1602595153.jpg) ### 什麼是分散式鎖 說到Redis,我們第一想到的功能就是可以快取資料,除此之外,Redis因為單程序、效能高的
面試官:你說說ReentrantLock和Synchronized區別
大家好!又和大家見面了。為了避免面試尷尬,今天同比較通俗語言和大家聊下ReentrantLock和Synchronized區別! 使用方式 Synchronized可以修飾例項方法,靜態方法,程式碼塊。自動釋放鎖。 ReentrantLock一般需要try catch finally語句,在t
面試問題:你瞭解Java記憶體模型麼(Java7、8、9記憶體模型的區別)
Java記憶體模型是每個java程式設計師必須掌握理解的,這是Java的核心基礎,對我們編寫程式碼特別是併發程式設計時有很大幫助。由於Java程式是交由JVM執行的,所以我們在談Java記憶體區域劃分的時候事實上是指JVM記憶體區域劃分。 首先,我們回顧一下Java程式執行