題目要求
根據需求實現一個緩存池,當請求第一次加載的時候,計算緩存值,並存入緩存中,當另一請求來的時候,直接從緩存中獲取對應值,避免重復計算,注意只允許第一次的請求進入計算過程:
實現思路
通過map實現緩存的功能,通過加鎖的方式實現只有一個請求能夠進入到計算的流程中
- 緩存工具類
package com.ijianghu.basetype.concurrent;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* 設計一個緩存類,當多個線程調用的時候,第一個線程進行計算,並放入緩存;
* 其他線程直接返回已有的緩存
*
*/
public class CacheUtils {
/**
* 緩存map
*/
private HashMap<String,String> cacheMap = new HashMap();
private ReentrantLock lock = new ReentrantLock();
/**
* 獲取緩存值,只有一個線程能進來
* @param key
* @return
*/
public String getCacheValue(String key){
if(Objects.isNull(cacheMap.get(key))){
lock.lock();
if(Objects.isNull(cacheMap.get(key))){
String value = calculateValue(key);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
cacheMap.put(key,value);
Set<Map.Entry<String, String>> entries = cacheMap.entrySet();
for (Map.Entry<String, String> entry: entries) {
System.out.println("key:".concat(entry.getKey()).concat("---------value:").concat(entry.getValue()));
}
lock.unlock();
return value;
}
else{
lock.unlock();
System.out.println(Thread.currentThread().getName().concat(key+"等待得到返回值:".concat(cacheMap.get(key))));
return cacheMap.get(key);
}
}else{
System.out.println(Thread.currentThread().getName().concat("key+直接得到返回值:".concat(cacheMap.get(key))));
return cacheMap.get(key);
}
}
public String calculateValue(String key){
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(key.getBytes("UTF-8"));
byte[] digestByte = digest.digest();
System.out.println("digestByte的長度:"+digestByte.length);
String value = byteToHex(digestByte);
System.out.println(Thread.currentThread().getName()+key+"計算緩存值:".concat(value));
return value;
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
private String byteToHex(byte[] digestByte) {
// 用來將字節轉換成 16 進制表示的字符
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char str[] = new char[16*2];
int k = 0;//表示轉換結果中對應的字符位置
for(int i=0;i<16;i++){
byte byte0 = digestByte[i];//取第i個字節
str[k++] = hexDigits[byte0>>> 4 & 0xf];//取字節中高4位的數字轉換,>>> 邏輯右移,將符號位一起右移;
str[k++] = hexDigits[byte0 & 0xf];//取字節中低4位的數字轉換
}
return new String(str);
}
}
-
模擬調用 1
在調用過程中,為了是主線程等待子線程先執行完,可以有多重實現方法,此類中包含了兩種方式:
1、使用CountDownLatch,使一個線程等待其他線程執行完,再執行;注意使用時,在執行過程中最后在調用countDown()方法
2、調用線程的join()方法,使主線程進入等待狀態(new 、runnable、blocked、waiting、time-waiting、terminated)
package com.ijianghu.basetype.concurrent;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/**
* @Description:create
* @author: ext.liukai3
* @date: 2021/6/2 11:08
*
* Thread.join()實現主線程阻塞
*
* 使用CountDownLatch實現主線程阻塞
*/
public class CacheDemo {
public static void main(String[] args) {
CacheUtils cacheUtils = new CacheUtils();
String[] str = new String[]{"a","b","c","d","e"};
long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(100);
System.out.println("start:"+start);
for(int i=0;i<100;i++){
Thread thread = new Thread(new CacheThread(str[i%5],cacheUtils,countDownLatch));
try {
Thread.sleep(2);
thread.start();
// thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end:"+end+"共消耗:"+(end-start));
}
}
class CacheThread extends Thread{
private CountDownLatch countDownLatch;
private CacheUtils cacheUtils;
private String key;
public CacheThread(String key,CacheUtils cacheUtils){
this.key = key;
this.cacheUtils = cacheUtils;
}
public CacheThread(String key,CacheUtils cacheUtils,CountDownLatch countDownLatch){
this(key,cacheUtils);
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
cacheUtils.getCacheValue(key);
if(Objects.nonNull(countDownLatch)){
countDownLatch.countDown();
}
}
}
-
模擬調用2
通過FutureTask.get()方法阻塞主線程
/**
*/
public class CacheFutureTask implements Callable<String> {
private CacheUtils cacheUtils;
private String key;
public CacheFutureTask(String key,CacheUtils cacheUtils){
this.cacheUtils = cacheUtils;
this.key = key;
}
@Override
public String call() throws Exception {
String cacheValue = cacheUtils.getCacheValue(key);
return cacheValue;
}
}
/**
*
* FutureTask 使用get()可以實現主線程阻塞
*/
public class CacheFutureDemo {
public static void main(String[] args) {
CacheUtils cacheUtils = new CacheUtils();
String[] str = new String[]{"a","b","c","d","e"};
long start = System.currentTimeMillis();
for(int i=0;i<100;i++){
CacheFutureTask futureTask = new CacheFutureTask(str[i % 5], cacheUtils);
FutureTask<String> task = new FutureTask<>(futureTask);
new Thread(task).start();
String s = null;
try {
s = task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(s);
}
long end = System.currentTimeMillis();
System.out.println("end:"+end+"共消耗:"+(end-start));
}
}
