题目要求
根据需求实现一个缓存池,当请求第一次加载的时候,计算缓存值,并存入缓存中,当另一请求来的时候,直接从缓存中获取对应值,避免重复计算,注意只允许第一次的请求进入计算过程:
实现思路
通过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));
}
}