SpringBoot+Redis實現(xiàn)分布式緩存的方法步驟
背景
緩存(Cache):指將程序或系統(tǒng)中常用的數(shù)據(jù)對象存儲在像內存這樣特定的介質中,以避免在每次程序調用時,重新創(chuàng)建或組織數(shù)據(jù)所帶來的性能損耗,從而提高了系統(tǒng)的整體運行速度。
以目前的系統(tǒng)架構來說,用戶的請求一般會先經(jīng)過緩存系統(tǒng),如果緩存中沒有相關的數(shù)據(jù),就會在其他系統(tǒng)中查詢到相應的數(shù)據(jù)并保存在緩存中,最后返回給調用方。
本地緩存:指程序級別的緩存組件,它的特點是本地緩存和應用程序會運行在同一個進程中,所以本地緩存的操作會非常快,因為在同一個進程內也意味著不會有網(wǎng)絡上的延遲和開銷。
本地緩存適用于單節(jié)點非集群的應用場景,它的優(yōu)點是快,缺點是多程序無法共享緩存,比如分布式用戶 Session 會話信息保存,由于每次用戶訪問的服務器可能是不同的,如果不能共享緩存,那么就意味著每次的請求操作都有可能被系統(tǒng)阻止,因為會話信息只保存在某一個服務器上,當請求沒有被轉發(fā)到這臺存儲了用戶信息的服務器時,就會被認為是非登錄的違規(guī)操作。
除此之外,無法共享緩存可能會造成系統(tǒng)資源的浪費,這是因為每個系統(tǒng)都單獨維護了一份屬于自己的緩存,而同一份緩存有可能被多個系統(tǒng)單獨進行存儲,從而浪費了系統(tǒng)資源。
分布式緩存:指將應用系統(tǒng)和緩存組件進行分離的緩存機制,這樣多個應用系統(tǒng)就可以共享一套緩存數(shù)據(jù)了,它的特點是共享緩存服務和可集群部署,為緩存系統(tǒng)提供了高可用的運行環(huán)境,以及緩存共享的程序運行機制。
本地緩存可以使用EhCache 和 Google 的 Guava來實現(xiàn),而分布式緩存可以使用 Redis 或 Memcached 來實現(xiàn)。
由于 Redis 本身就是獨立的緩存系統(tǒng),因此可以作為第三方來提供共享的數(shù)據(jù)緩存,而 Redis 的分布式支持主從、哨兵和集群的模式,所以它就可以支持分布式的緩存,而 Memcached 的情況也是類似的。
分布式緩存常見文件及解決方案
分布式緩存設計的核心問題是以哪種方式進行緩存預熱和緩存更新, 以及如何優(yōu)雅解決緩存雪崩、緩存穿透、緩存降級等問題。這些問題在不 同的應用場景下有不同的解決方案。
緩存預熱: 緩存預熱指在用戶請求數(shù)據(jù)前先將數(shù)據(jù)加載到緩存系統(tǒng)中,用戶查詢 事先被預熱的緩存數(shù)據(jù),以提高系統(tǒng)查詢效率。緩存預熱一般有系統(tǒng)啟動 加載、定時加載等方式。
緩存更新: 緩存更新指在數(shù)據(jù)發(fā)生變化后及時將變化后的數(shù)據(jù)更新到緩存中。常 見的緩存更新策略有以下4種。
- 定時更新:定時將底層數(shù)據(jù)庫內的數(shù)據(jù)更新到緩存中,該方法比較 簡單,適合需要緩存的數(shù)據(jù)量不是很大的應用場景。
- 過期更新:定時將緩存中過期的數(shù)據(jù)更新為最新數(shù)據(jù)并更新緩存的 過期時間。
- 寫請求更新:在用戶有寫請求時先寫數(shù)據(jù)庫同時更新緩存,這適用 于用戶對緩存數(shù)據(jù)和數(shù)據(jù)庫的數(shù)據(jù)有實時強一致性要求的情況。
- 讀請求更新:在用戶有讀請求時,先判斷該請求數(shù)據(jù)的緩存是否存 在或過期,如果不存在或已過期,則進行底層數(shù)據(jù)庫查詢并將查詢結果更 新到緩存中,同時將查詢結果返回給用戶。
緩存淘汰策略 在緩存數(shù)據(jù)過多時需要使用某種淘汰算法決定淘汰哪些數(shù)據(jù)。常用的 淘汰算法有以下幾種。
- FIFO(First In First Out,先進先出):判斷被存儲的時間,離 目前最遠的數(shù)據(jù)優(yōu)先被淘汰。
- LRU(Least Recently Used,最近最少使用):判斷緩存最近被使 用的時間,距離當前時間最遠的數(shù)據(jù)優(yōu)先被淘汰。
- LFU(Least Frequently Used,最不經(jīng)常使用):在一段時間內, 被使用次數(shù)最少的緩存優(yōu)先被淘汰。
緩存雪崩
緩存雪崩指在同一時刻由于大量緩存失效,導致大量原本應該訪問緩 存的請求都去查詢數(shù)據(jù)庫,而對數(shù)據(jù)庫的CPU和內存造成巨大壓力,嚴重的 話會導致數(shù)據(jù)庫宕機,從而形成一系列連鎖反應,使整個系統(tǒng)崩潰。
解決方案:
- 請求加鎖:對于并發(fā)量不是很多的應用,使用請求加鎖排隊的方案 防止過多請求數(shù)據(jù)庫。
- 失效更新:為每一個緩存數(shù)據(jù)都增加過期標記來記錄緩存數(shù)據(jù)是否 失效,如果緩存標記失效,則更新數(shù)據(jù)緩存。
- 緩存數(shù)據(jù)的過期時間設置隨機:為不同的數(shù)據(jù)設置不同的緩存失效時間,防止在同一時刻有大量的數(shù)據(jù)失效。
- 如果緩存數(shù)據(jù)庫是分布式部署,將熱點數(shù)據(jù)均勻分布在不同的緩存數(shù)據(jù)庫中。
- 設置熱點數(shù)據(jù)永遠不過期。
緩存穿透
緩存穿透指由于緩存系統(tǒng)故障或者用戶頻繁查詢系統(tǒng)中不存在(在系 統(tǒng)中不存在,在自然數(shù)據(jù)庫和緩存中都不存在)的數(shù)據(jù),而這時請求穿過 緩存不斷被發(fā)送到數(shù)據(jù)庫,導致數(shù)據(jù)庫過載,進而引發(fā)一連串并發(fā)問題。 比如用戶發(fā)起一個userName為zhangsan的請求,而在系統(tǒng)中并沒有名 為zhangsan的用戶,這樣就導致每次查詢時在緩存中都找不到該數(shù)據(jù),然 后去數(shù)據(jù)庫中再查詢一遍。由于zhangsan用戶本身在系統(tǒng)中不存在,自然 返回空,導致請求穿過緩存頻繁查詢數(shù)據(jù)庫,在用戶頻繁發(fā)送該請求時將 導致數(shù)據(jù)庫系統(tǒng)負載增大,從而可能引發(fā)其他問題。常用的解決緩存穿透 問題的方法有布隆過濾器和cache null策略。
解決方案:
- 接口層增加校驗,如用戶鑒權校驗,id做基礎校驗,id<=0的直接攔截,一定不存在的不去查詢數(shù)據(jù)庫。
- 布隆過濾器:指將所有可能存在的數(shù)據(jù)都映射到一個足夠大的 Bitmap中,在用戶發(fā)起請求時首先經(jīng)過布隆過濾器的攔截,一個一定不存 在的數(shù)據(jù)會被這個布隆過濾器攔截,從而避免對底層存儲系統(tǒng)帶來查詢上 的壓力。
- cache null策略:指如果一個查詢返回的結果為null(可能是數(shù)據(jù) 不存在,也可能是系統(tǒng)故障),我們仍然緩存這個null結果,但它的過期 時間會很短,通常不超過 5 分鐘;在用戶再次請求該數(shù)據(jù)時直接返回 null,而不會繼續(xù)訪問數(shù)據(jù)庫,從而有效保障數(shù)據(jù)庫的安全。其實cache null策略的核心原理是:在緩存中記錄一個短暫的(數(shù)據(jù)過期時間內)數(shù) 據(jù)在系統(tǒng)中是否存在的狀態(tài),如果不存在,則直接返回null,不再查詢數(shù) 據(jù)庫,從而避免緩存穿透到數(shù)據(jù)庫上。
緩存擊穿
緩存擊穿是指緩存中沒有但數(shù)據(jù)庫中有的數(shù)據(jù)(一般是緩存時間到期),這時由于并發(fā)用戶特別多,同時讀緩存沒讀到數(shù)據(jù),又同時去數(shù)據(jù)庫去取數(shù)據(jù),引起數(shù)據(jù)庫壓力瞬間增大,造成過大壓力
解決方案:
- 設置熱點數(shù)據(jù)永遠不過期。
- 加互斥鎖。
緩存降級
緩存降級指由于訪問量劇增導致服務出現(xiàn)問題(如響應時間慢或不響 應)時,優(yōu)先保障核心業(yè)務的運行,減少或關閉非核心業(yè)務對資源的使 用。
服務降級策略:
- 寫降級:在寫請求增大時,可以只進行Cache的更新,然后將數(shù)據(jù) 異步更新到數(shù)據(jù)庫中,保證最終一致性即可,即將寫請求從數(shù)據(jù)庫降級為 Cache。
- 讀降級:在數(shù)據(jù)庫服務負載過高或數(shù)據(jù)庫系統(tǒng)故障時,可以只對 Cache進行讀取并將結果返回給用戶,在數(shù)據(jù)庫服務正常后再去查詢數(shù)據(jù) 庫,即將讀請求從數(shù)據(jù)庫降級為Cache。這種方式適用于對數(shù)據(jù)實時性要求 不高的場景,保障了在系統(tǒng)發(fā)生故障的情況下用戶依然能夠訪問到數(shù)據(jù), 只是訪問到的數(shù)據(jù)相對有延遲。
一、在pom中添加依賴
<!--springboot redis依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
二、在配置文件中配置Redis連接
# Redis數(shù)據(jù)庫索引(默認為0) spring.redis.database=0 # Redis服務器地址 spring.redis.host=127.0.0.1 # Redis服務器連接端口 spring.redis.port=6379 # Redis服務器連接密碼(默認為空) spring.redis.password=123456 # 連接池最大連接數(shù)(使用負值表示沒有限制) spring.redis.jedis.pool.max-idle=10 # 連接池最大阻塞等待時間(使用負值表示沒有限制) spring.redis.jedis.pool.max-wait=3000 # 連接池中的最小空閑連接 spring.redis.jedis.pool.min-idle=0 # 連接超時時間(毫秒) spring.redis.timeout=3000
三、編寫Redis配置文件
@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { /** * RedisTemplate相關配置 * 使redis支持插入對象 * * @param factory * @return 方法緩存 Methods the cache */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); // 配置連接工廠 template.setConnectionFactory(factory); // 序列化和反序列化redis的value值(默認使用JDK的序列化方式) Jackson2JsonRedisSerializer jacksonSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); // 指定要序列化的域,field,get和set,以及修飾符范圍,ANY是都有包括private和public om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會跑出異常 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSerializer.setObjectMapper(om); // 值采用json序列化 template.setValueSerializer(jacksonSerializer); // 使用StringRedisSerializer來序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); // 設置hash key 和value序列化模式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jacksonSerializer); template.afterPropertiesSet(); return template; } }
四、測試Redis緩存
/** * @Author: oyc * @Description: redis 測試控制類 * @Since 2020年5月12日 23:35:05 */ @RestController @RequestMapping("/redis") public class OyRedisController { /** * 依賴注入,注入redisTemplate */ @Autowired private RedisTemplate redisTemplate; /** * 測試redis string add */ @GetMapping("/string/add") public String addStringKeyValue(@RequestParam(value = "key", defaultValue = "key1") String key, @RequestParam(value = "value", defaultValue = "redis value") String value) { redisTemplate.opsForValue().set(key, value); return (String) redisTemplate.opsForValue().get(key); } /** * 測試redis string add */ @GetMapping("/object/add") public Object addObjectKeyValue(@RequestParam(value = "key", defaultValue = "key1") String key) { OyUser user = new OyUser(1, "宋江", "18", "male"); redisTemplate.opsForValue().set(key, user); return redisTemplate.opsForValue().get(key); } /** * 測試redis string get */ @GetMapping("/string/get") public Object getStringByKey(@RequestParam(value = "key", defaultValue = "key1") String key) { return redisTemplate.opsForValue().get(key); } }
4.1 測試key、value 為string
4.1.1 使用接口往redis添加記錄:
4.1.2 使用IDEA的redis客戶端插件查看記錄:
4.1.3 使用接口獲取緩存數(shù)據(jù):
4.2 測試key為string,value 為對象
五、工具類
上面案例都是直接用RedisTemplate操作Redis,操作比較復雜,借鑒網(wǎng)友寫了一個RedisUtils工具類,RedisUtils交給Spring容器實例化,使用時直接注解注入,使用方便簡單,減少使用難度。
/** * @Description: redisTemplate封裝 * Redis支持五種數(shù)據(jù)類型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。 * @Author: oyc * @Since 2020年5月12日 23:41:08 */ @Component public class RedisUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; public RedisUtil(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } //==========================基本操作=============================== /** * 指定緩存失效時間 * * @param key 鍵 * @param time 時間(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根據(jù)key 獲取過期時間 * * @param key 鍵 不能為null * @return 時間(秒) 返回0代表為永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判斷key是否存在 * * @param key 鍵 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 刪除緩存 * * @param key 可以傳一個值 或多個 */ public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } /** * 模糊查詢獲取key值 * * @param pattern * @return */ public Set keys(String pattern) { return redisTemplate.keys(pattern); } /** * 使用Redis的消息隊列 * * @param channel * @param message 消息內容 */ public void convertAndSend(String channel, Object message) { redisTemplate.convertAndSend(channel, message); } //============================String============================= /** * 普通緩存獲取 * * @param key 鍵 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通緩存放入 * * @param key 鍵 * @param value 值 * @return true成功 false失敗 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通緩存放入并設置時間 * * @param key 鍵 * @param value 值 * @param time 時間(秒) time要大于0 如果time小于等于0 將設置無限期 * @return true成功 false 失敗 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 遞增 * * @param key 鍵 * @param delta 要增加幾(大于0) * @return */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("遞增因子必須大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 遞減 * * @param key 鍵 * @param delta 要減少幾(小于0) * @return */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("遞減因子必須大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } //================================hash================================= /** * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建 * * @param key 鍵 * @param item 項 * @param value 值 * @return true 成功 false失敗 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建 * * @param key 鍵 * @param item 項 * @param value 值 * @param time 時間(秒) 注意:如果已存在的hash表有時間,這里將會替換原有的時間 * @return true 成功 false失敗 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashGet * * @param key 鍵 不能為null * @param item 項 不能為null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 刪除hash表中的值 * * @param key 鍵 不能為null * @param item 項 可以使多個 不能為null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 獲取hashKey對應的所有鍵值 * * @param key 鍵 * @return 對應的多個鍵值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * @param key 鍵 * @param map 對應多個鍵值 * @return true 成功 false 失敗 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并設置時間 * * @param key 鍵 * @param map 對應多個鍵值 * @param time 時間(秒) * @return true成功 false失敗 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 判斷hash表中是否有該項的值 * * @param key 鍵 不能為null * @param item 項 不能為null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash遞增 如果不存在,就會創(chuàng)建一個 并把新增后的值返回 * * @param key 鍵 * @param item 項 * @param by 要增加幾(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash遞減 * * @param key 鍵 * @param item 項 * @param by 要減少記(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } //============================set============================= /** * 根據(jù)key獲取Set中的所有值 * * @param key 鍵 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根據(jù)value從一個set中查詢,是否存在 * * @param key 鍵 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 將數(shù)據(jù)放入set緩存 * * @param key 鍵 * @param values 值 可以是多個 * @return 成功個數(shù) */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 將set數(shù)據(jù)放入緩存 * * @param key 鍵 * @param time 時間(秒) * @param values 值 可以是多個 * @return 成功個數(shù) */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) { expire(key, time); } return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 獲取set緩存的長度 * * @param key 鍵 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值為value的 * * @param key 鍵 * @param values 值 可以是多個 * @return 移除的個數(shù) */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } //===============================list================================= /** * 獲取list緩存的內容 * * @param key 鍵 * @param start 開始 * @param end 結束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 獲取list緩存的長度 * * @param key 鍵 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通過索引 獲取list中的值 * * @param key 鍵 * @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數(shù)第二個元素,依次類推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 將list放入緩存 * * @param key 鍵 * @param value 值 * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 將list放入緩存 * * @param key 鍵 * @param value 值 * @param time 時間(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 將list放入緩存 * * @param key 鍵 * @param value 值 * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 將list放入緩存 * * @param key 鍵 * @param value 值 * @param time 時間(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根據(jù)索引修改list中的某條數(shù)據(jù) * * @param key 鍵 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N個值為value * * @param key 鍵 * @param count 移除多少個 * @param value 值 * @return 移除的個數(shù) */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }
源碼: https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-redis
到此這篇關于SpringBoot+Redis 實現(xiàn)分布式緩存的方法步驟的文章就介紹到這了,更多相關SpringBoot Redis 分布式緩存內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
ThreadPoolExecutor中的submit()方法詳細講解
在使用線程池的時候,發(fā)現(xiàn)除了execute()方法可以執(zhí)行任務外,還發(fā)現(xiàn)有一個方法submit()可以執(zhí)行任務,本文就詳細的介紹一下ThreadPoolExecutor中的submit()方法,具有一定的參考價值,感興趣的可以了解一下2022-04-04java.net.SocketTimeoutException: Read timed o
本文主要介紹了java.net.SocketTimeoutException: Read timed out異常的解決,可能是因為網(wǎng)絡延遲、服務器響應慢或連接不穩(wěn)定等原因造成的,下面就一起來介紹一下,感興趣的可以了解一下2024-05-05Java 實現(xiàn)加密數(shù)據(jù)庫連接的步驟
這篇文章主要介紹了Java 實現(xiàn)加密數(shù)據(jù)庫連接的步驟,幫助大家更好的理解和使用Java處理數(shù)據(jù)庫,感興趣的朋友可以了解下2020-11-11Druid基本配置及內置監(jiān)控使用_動力節(jié)點Java學院整理
這篇文章主要介紹了Druid基本配置及內置監(jiān)控使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08