面试精选

Java并发编程:ConcurrentHashMap的使用,实现

2020-12-24  本文已影响0人  singleZhang2010

概述

ConcurrentHashMap 是线程安全且高效的HashMap

问:为什么使用ConcurrentHashMap?

在并发编程中使用HashMap,会出现ConcurrentModificationException异常、会引起程序死循环等情况。使用线程安全的Hashtable的话效率太低,ConcurrentHashMap保证了线程安全的同时又保证了高效的操作。

多线程下,HashMap为什么会出现死循环?

HashMap出现死循环java7和java8不太一样。
java7出现在扩容的场合,因为头插法导致链表死循环,java8使用了数组+链表+红黑树 以及尾插法修复了这个问题,不过在PutTreeValue的时候可能会出现Node节点转换为TreeNode结点异常,所以多线程下不要使用HashMap。

使用ConcurrentHashMap实现本地缓存

1.创建包:com.zhxin.threadLab.concurrenthashmap.chapter1,并创建缓存实体类CacheObj

package com.zhxin.threadLab.concurrenthashmap.chapter1;

/**
 * @ClassName CacheObj
 * @Description //缓存对象类
 * @Author singleZhang
 * @Email 405780096@qq.com
 * @Date 2020/12/24 0024 下午 3:30
 **/
public class CacheObj {
    /**
     * 缓存对象
     */
    private Object CacheValue;
    /**
     * 缓存过期时间
     */
    private Long expireTime;

    CacheObj(Object cacheValue, Long expireTime) {
        CacheValue = cacheValue;
        this.expireTime = expireTime;
    }

    Object getCacheValue() {
        return CacheValue;
    }

    Long getExpireTime() {
        return expireTime;
    }

    @Override
    public String toString() {
        return "CacheObj {" +
                "CacheValue = " + CacheValue +
                ", expireTime = " + expireTime +
                '}';
    }
}

  1. 创建过期缓存清理线程类CleanCacheThread
package com.zhxin.threadLab.concurrenthashmap.chapter1;

/**
 * @ClassName CleanCacheThread
 * @Description //清理过期缓存线程
 * @Author singleZhang
 * @Email 405780096@qq.com
 * @Date 2020/12/24 0024 下午 3:37
 **/
public class CleanCacheThread implements Runnable{

    @Override
    public void run() {
        ConcurrentHashMapCacheUtil.setCleanThreadRun();
        while (true) {
            System.out.println("clean thread run ");
            ConcurrentHashMapCacheUtil.deleteTimeOut();
            try {
                Thread.sleep(ConcurrentHashMapCacheUtil.ONE_MINUTE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
  1. 创建ConcurrentHashMap缓存工具类ConcurrentHashMapCacheUtil
package com.zhxin.threadLab.concurrenthashmap.chapter1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName ConcurrentHashMapCacheUtil
 * @Description //使用ConcurrentHashMap 做本地缓存
 * @Author singleZhang
 * @Email 405780096@qq.com
 * @Date 2020/12/24 0024 下午 3:23
 **/
public class ConcurrentHashMapCacheUtil {

    private static Logger LOGGER = LoggerFactory.getLogger(ConcurrentHashMapCacheUtil.class);

    /**
     * 缓存最大个数
     */
    private static final Integer CACHE_MAX_NUMBER = 1000;
    /**
     * 当前缓存个数
     */
    private static Integer CURRENT_SIZE = 0;
    /**
     * 时间一分钟
     */
    static final Long ONE_MINUTE = 60 * 1000L;
    /**
     * 缓存对象
     */
    private static final Map<String, CacheObj> CACHE_OBJECT_MAP = new ConcurrentHashMap<>();
    /**
     * 这个记录了缓存使用的最后一次的记录,最近使用的在最前面
     */
    private static final List<String> CACHE_USE_LOG_LIST = new LinkedList<>();
    /**
     * 清理过期缓存是否在运行
     */
    private static volatile Boolean CLEAN_THREAD_IS_RUN = false;

    /**
     * 设置缓存
     */
    public static void setCache(String cacheKey, Object cacheValue, long cacheTime) {
        Long expireTime = null;
        if (cacheTime <= 0L) {
            if (cacheTime == -1L) {
                expireTime = -1L;
            } else {
                return;
            }
        }
        checkSize();
        saveCacheUseLog(cacheKey);
        CURRENT_SIZE = CURRENT_SIZE + 1;
        if (expireTime == null) {
            expireTime = System.currentTimeMillis() + cacheTime;
        }
        CacheObj cacheObj = new CacheObj(cacheValue, expireTime);
        CACHE_OBJECT_MAP.put(cacheKey, cacheObj);
        LOGGER.info("have set key :" + cacheKey);
    }
    /**
     * 设置缓存
     */
    public static void setCache(String cacheKey, Object cacheValue) {
        setCache(cacheKey, cacheValue, -1L);
    }
    /**
     * 获取缓存
     */
    public static Object getCache(String cacheKey) {
        startCleanThread();
        if (checkCache(cacheKey)) {
            saveCacheUseLog(cacheKey);
            return CACHE_OBJECT_MAP.get(cacheKey).getCacheValue();
        }
        return null;
    }
    public static boolean isExist(String cacheKey) {
        return checkCache(cacheKey);
    }
    /**
     * 删除所有缓存
     */
    public static void clear() {
        LOGGER.info("have clean all key !");
        CACHE_OBJECT_MAP.clear();
        CURRENT_SIZE = 0;
    }
    /**
     * 删除某个缓存
     */
    public static void deleteCache(String cacheKey) {
        Object cacheValue = CACHE_OBJECT_MAP.remove(cacheKey);
        if (cacheValue != null) {
            LOGGER.info("have delete key :" + cacheKey);
            CURRENT_SIZE = CURRENT_SIZE - 1;
        }
    }
    /**
     * 判断缓存在不在,过没过期
     */
    private static boolean checkCache(String cacheKey) {
        CacheObj cacheObj = CACHE_OBJECT_MAP.get(cacheKey);
        if (cacheObj == null) {
            return false;
        }
        if (cacheObj.getExpireTime() == -1L) {
            return true;
        }
        if (cacheObj.getExpireTime() < System.currentTimeMillis()) {
            deleteCache(cacheKey);
            return false;
        }
        return true;
    }
    /**
     * 删除最近最久未使用的缓存
     */
    private static void deleteLRU() {
        LOGGER.info("delete Least recently used run!");
        String cacheKey = null;
        synchronized (CACHE_USE_LOG_LIST) {
            if (CACHE_USE_LOG_LIST.size() >= CACHE_MAX_NUMBER - 10) {
                cacheKey = CACHE_USE_LOG_LIST.remove(CACHE_USE_LOG_LIST.size() - 1);
            }
        }
        if (cacheKey != null) {
            deleteCache(cacheKey);
        }
    }

    /**
     * 删除过期的缓存
     */
    static void deleteTimeOut() {
        LOGGER.info("delete time out run!");
        List<String> deleteKeyList = new LinkedList<>();
        for(Map.Entry<String, CacheObj> entry : CACHE_OBJECT_MAP.entrySet()) {
            if (entry.getValue().getExpireTime() < System.currentTimeMillis() && entry.getValue().getExpireTime() != -1L) {
                deleteKeyList.add(entry.getKey());
            }
        }
        for (String deleteKey : deleteKeyList) {
            deleteCache(deleteKey);
        }
        LOGGER.info("delete cache count is :" + deleteKeyList.size());
    }
    /**
     * 检查大小
     * 当当前大小如果已经达到最大大小
     * 首先删除过期缓存,如果过期缓存删除过后还是达到最大缓存数目
     * 删除最久未使用缓存
     */
    private static void checkSize() {
        if (CURRENT_SIZE >= CACHE_MAX_NUMBER) {
            deleteTimeOut();
        }
        if (CURRENT_SIZE >= CACHE_MAX_NUMBER) {
            deleteLRU();
        }
    }

    /**
     * 保存缓存的使用记录
     */
    private static synchronized void saveCacheUseLog(String cacheKey) {
        synchronized (CACHE_USE_LOG_LIST) {
            CACHE_USE_LOG_LIST.remove(cacheKey);
            CACHE_USE_LOG_LIST.add(0,cacheKey);
        }
    }

    /**
     * 设置清理线程的运行状态为正在运行
     */
    static void setCleanThreadRun() {
        CLEAN_THREAD_IS_RUN = true;
    }

    /**
     * 开启清理过期缓存的线程
     */
    private static void startCleanThread() {
        if (!CLEAN_THREAD_IS_RUN) {
            CleanCacheThread cleanCacheThread = new CleanCacheThread();
            Thread thread = new Thread(cleanCacheThread);
            //设置为后台守护线程
            thread.setDaemon(true);
            thread.start();
        }
    }
}

如此,就完成了一个简单的缓存类

上一篇下一篇

猜你喜欢

热点阅读