并发编程中的ConcurrentHashMap

简介: 并发编程中的ConcurrentHashMap

ConcurrentHashMap是Java中并发编程中常用的一种线程安全的哈希表,它是对Hashtable的替代方案,相比于Hashtable能够更好地支持并发访问。

CSDN给的优缺点

优点:
  • 线程安全:ConcurrentHashMap中的所有方法都是线程安全的;
  • 高效性:ConcurrentHashMap使用分段锁技术,可以有效地缩小锁的范围,提高了并发访问的性能;
  • 支持高并发:ConcurrentHashMap中每个Segment都相互独立,因此可以支持更高的并发量;
缺点:
  • 内存占用:ConcurrentHashMap的分段锁机制增加了内存的占用;
  • 不支持键值为null:ConcurrentHashMap的键值不支持为null,如果需要使用null,可以使用ConcurrentSkipListMap。

使用场景:

  • 多线程并发访问:ConcurrentHashMap适用于多线程并发访问的场景;
  • 高并发量:ConcurrentHashMap适用于需要支持高并发量的场景;
  • 缓存:ConcurrentHashMap可以用于实现缓存,可以将缓存数据存储在其中。

对应方法

1、设置缓存

2、获取缓存

3、删除所有缓存

4、删除单个缓存

5、判断缓存在不在,过没过期

6、删除最近最久未使用的缓存

7、删除过期的缓存

8、检查大小

9、保存缓存的使用记录

10、设置清理线程的运行状态为正在运行

11、开启清理过期缓存的线程

import lombok.Data;
 
import java.io.Serializable;
 
/**
 * 缓存
 */
@Data
public class CacheObj implements Serializable {
    private static final long serialVersionUID = 5272376851165037333L;
    /**
     * 缓存对象
     */
    private Object cacheValue;
    /**
     * 缓存过期时间
     */
    private Long ttlTime;
 
    public CacheObj(Object cacheValue, Long ttlTime) {
        this.cacheValue = cacheValue;
        this.ttlTime = ttlTime;
    }
 
    @Override
    public String toString() {
        return "CacheObj {" +
                "cacheValue = " + cacheValue +
                ", ttlTime = " + ttlTime +
                '}';
    }
}
 
import lombok.extern.slf4j.Slf4j;
 
/**
 * 每一分钟清理一次过期缓存
 */
@Slf4j
public class CleanTimeOutThread implements Runnable {
    @Override
    public void run() {
        ConcurrentHashMapCacheUtil.setCleanThreadRun();
        while (true) {
            log.warn("clean thread run ");
            ConcurrentHashMapCacheUtil.deleteTimeOut();
            try {
                Thread.sleep(ConcurrentHashMapCacheUtil.ONE_MINUTE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
 
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * 本地缓存
 */
@Slf4j
@Component
public class ConcurrentHashMapCacheUtil {
    /**
     * 缓存最大个数
     */
    private static final Integer CACHE_MAX_NUMBER = 50000;
    /**
     * 当前缓存个数
     */
    private static Integer CURRENT_SIZE = 0;
    /**
     * 时间一分钟
     */
    static final Long ONE_MINUTE = 60 * 1000L;
    /**
     * 永不过期
     */
    private static final Long CACHE_FOREVER = -1L;
    /**
     * 缓存对象
     */
    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;
 
 
    /**
     * 设置缓存
     *
     * @param cacheKey
     * @param cacheValue
     * @param cacheTime
     */
    public static void setCache(String cacheKey, Object cacheValue, long cacheTime) {
        Long ttlTime = null;
        if (cacheTime <= 0L) {
            if (cacheTime == -1L) {
                ttlTime = -1L;
            } else {
                return;
            }
        }
//        checkSize();
//        saveCacheUseLog(cacheKey);
        CURRENT_SIZE = CURRENT_SIZE + 1;
        if (ttlTime == null) {
            ttlTime = System.currentTimeMillis() + cacheTime;
        }
        CacheObj cacheObj = new CacheObj(cacheValue, ttlTime);
        CACHE_OBJECT_MAP.put(cacheKey, cacheObj);
        log.info("have set key :{}", cacheKey);
    }
 
    /**
     * 设置缓存
     *
     * @param cacheKey
     * @param cacheValue
     */
    public static void setCache(String cacheKey, Object cacheValue) {
        setCache(cacheKey, cacheValue, CACHE_FOREVER);
    }
 
    /**
     * 获取缓存
     *
     * @param cacheKey
     * @return
     */
    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() {
        log.info("have clean all key !");
        CACHE_OBJECT_MAP.clear();
        CURRENT_SIZE = 0;
    }
 
    /**
     * 删除单个缓存
     *
     * @param cacheKey
     */
    public static void deleteCache(String cacheKey) {
        Object cacheValue = CACHE_OBJECT_MAP.remove(cacheKey);
        if (cacheValue != null) {
            log.info("have delete key : {}", cacheKey);
            CURRENT_SIZE = CURRENT_SIZE - 1;
        }
    }
 
    /**
     * 判断缓存在不在,过没过期
     *
     * @param cacheKey
     * @return
     */
    private static boolean checkCache(String cacheKey) {
        CacheObj cacheObj = CACHE_OBJECT_MAP.get(cacheKey);
        if (cacheObj == null) {
            return false;
        }
        if (cacheObj.getTtlTime() == -1L) {
            return true;
        }
        if (cacheObj.getTtlTime() < System.currentTimeMillis()) {
            deleteCache(cacheKey);
            return false;
        }
        return true;
    }
 
    /**
     * 删除最近最久未使用的缓存
     */
    private static void deleteLRU() {
        log.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);
        }
    }
 
    /**
     * 删除过期的缓存
     */
    public static void deleteTimeOut() {
        log.info("delete time out run!");
        List<String> deleteKeyList = new LinkedList<>();
        for (Map.Entry<String, CacheObj> entry : CACHE_OBJECT_MAP.entrySet()) {
            if (entry.getValue().getTtlTime() < System.currentTimeMillis() && entry.getValue().getTtlTime() != -1L) {
                deleteKeyList.add(entry.getKey());
            }
        }
 
        for (String deleteKey : deleteKeyList) {
            deleteCache(deleteKey);
        }
        log.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();
        }
    }
 
    /**
     * 保存缓存的使用记录
     *
     * @param cacheKey
     */
    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);
        }
    }
 
    /**
     * 设置清理线程的运行状态为正在运行
     */
    public static void setCleanThreadRun() {
        CLEAN_THREAD_IS_RUN = true;
    }
 
    /**
     * 开启清理过期缓存的线程
     */
    private static void startCleanThread() {
        if (!CLEAN_THREAD_IS_RUN) {
            CleanTimeOutThread cleanTimeOutThread = new CleanTimeOutThread();
            Thread thread = new Thread(cleanTimeOutThread);
            //设置为后台守护线程
            thread.setDaemon(true);
            thread.start();
        }
    }
}


相关文章
|
6月前
|
存储 Java
JUC并发编程之深入理解ThreadLocal
ThreadLocal是Java标准库提供的一个工具类,位于java.lang包下。它允许你创建一个线程局部变量,每个线程都可以独立地访问自己的变量副本,互不干扰。这在某些场景下非常有用,比如在多线程环境下,每个线程需要维护自己的状态信息,但又不想通过方法参数传递的方式来实现。
|
4月前
|
存储 缓存 安全
ConcurrentHashMap在并发编程中的应用
ConcurrentHashMap在并发编程中的应用
|
5月前
|
存储 安全 Java
Java多线程中线程安全问题
Java多线程中的线程安全问题主要涉及多线程环境下对共享资源的访问可能导致的数据损坏或不一致。线程安全的核心在于确保在多线程调度顺序不确定的情况下,代码的执行结果始终正确。常见原因包括线程调度随机性、共享数据修改以及原子性问题。解决线程安全问题通常需要采用同步机制,如使用synchronized关键字或Lock接口,以确保同一时间只有一个线程能够访问特定资源,从而保持数据的一致性和正确性。
5684 1
|
6月前
|
编解码 安全 算法
Java多线程基础-18:线程安全的集合类与ConcurrentHashMap
如果这些单线程中的集合类确实需要在多线程中使用,该怎么办呢?思路有两个: 最直接的方式:使用锁,手动保证。如多个线程修改ArrayList对象,此时就可能有问题,就可以给修改操作进行加锁。但手动加锁的方式并不是很方便,因此标准库还提供了一些线程安全的集合类。
97 4
|
6月前
|
安全 Java 容器
Java一分钟之-并发编程:并发容器(ConcurrentHashMap, CopyOnWriteArrayList)
【5月更文挑战第18天】本文探讨了Java并发编程中的`ConcurrentHashMap`和`CopyOnWriteArrayList`,两者为多线程数据共享提供高效、线程安全的解决方案。`ConcurrentHashMap`采用分段锁策略,而`CopyOnWriteArrayList`适合读多写少的场景。注意,`ConcurrentHashMap`的`forEach`需避免手动同步,且并发修改时可能导致`ConcurrentModificationException`。`CopyOnWriteArrayList`在写操作时会复制数组。理解和正确使用这些特性是优化并发性能的关键。
62 1
|
6月前
|
存储 缓存 安全
Java 并发编程中的高效同步机制:深入理解 ConcurrentHashMap
在多线程环境下,高效的数据同步是确保程序正确性的关键。本文将深入分析 Java 中提供的一个高级并发工具——ConcurrentHashMap,探讨其设计原理、功能特性以及在实际开发中的应用。通过对其内部结构和工作机制的剖析,读者将了解为何 ConcurrentHashMap 能够在保证线程安全的同时,提供比传统同步手段更高的吞吐率和更佳的性能表现。
|
6月前
|
缓存 安全 Java
Java并发编程中的线程安全性探讨
【2月更文挑战第6天】在Java开发中,多线程编程是一种常见的方式,然而如何确保线程安全性却是一个复杂且关键的问题。本文将深入探讨Java并发编程中的线程安全性,包括线程安全性的概念、常见的线程安全性问题以及解决方法,旨在帮助开发者更好地理解和应对多线程环境下的挑战。
|
存储 安全 算法
Java并发编程之ConcurrentHashMap源码分析
HashMap多线程put后get为null和多线程put的时候可能导致元素丢失 在多线程环境下,使用HashMap进行put操作时存在丢失数据的情况,为了避免这种bug的隐患,强烈建议使用ConcurrentHashMap代替HashMap
250 0
Java并发编程之ConcurrentHashMap源码分析
|
安全 Java 调度
【并发编程】线程安全性问题
【并发编程】线程安全性问题
10171 0
|
缓存 安全 Java
Java多线程之线程安全问题
Java多线程之线程安全问题
191 0
Java多线程之线程安全问题