概述
ConcurrentHashMap
是Java中提供的一个关于线程安全的哈希表实现,他是java.util.concurrent
包的一部分,允许多个读操作并发进行,提高了并发环境下的性能。ConcurrentHashMap
实现了ConcurrentMap
接口,故而他也有ConcurrentMap
中的所有原子操作,例如putIfAbsent
、remove
和replace
。
ConcurrentMap
定义了一些重要的线程安全的原子操作,而ConcurrentHashMap
提供了这些操作的具体实现。因此,ConcurrentHashMap
继承了ConcurrentMap
的所有特性,并且在此基础上进行了优化和扩展,以支持更高效的并发操作。
底层实现
在Java8之前,ConcurrentHashMap使用分段技术,将数据分成一段一段,没一段都由自己的锁,当一个线程访问一个特定的段时候,只会锁定这个段,而不会影响其他段,这样就允许多个线程并发地访问不同的段。
在Java8的时候,ConcurrentHashMap的实现被重写了,不在使用分段锁,使用了一种基于节点的锁定策略,通过使用CAS(compare and Swap) 操作和synchronized关键字来减少锁的争用。在Java 8中,ConcurrentHashMap的数据结构是一个数组 + 链表 + 红黑树的复合结构,当链表长度超过一定的阈值时候,链表会转换为红黑树,从而提高搜索效率。
优缺点
优点
- 线程安全: 提供了线程安全的Map实现,适用于多线程环境。
- 高并发性能:相比于Hashtable和Collections.synchronizedMap,ConcurrentHashMap提供了更好的并发性能。
- 拓展性: 通过分离锁的机制,允许更多的并发更新操作。
- 原子操作:支持多种原子操作,简化了并发编程。
缺点
- 内存占用: 为了实现线程安全,可能会比非线程安全的Map实现使用更多的内存。
- 复杂性:内部实现比较复杂,理解和使用不当会导致性能问题。
案例:
模拟实现一个简单的线程安全的缓存系统
import java.util.concurrent.ConcurrentHashMap;
public class SimpleCache<K, V> {
private final ConcurrentHashMap<K, V> cacheMap = new ConcurrentHashMap<>();
public V get(K key) {
return cacheMap.get(key);
}
public void put(K key, V value) {
cacheMap.put(key, value);
}
public void remove(K key) {
cacheMap.remove(key);
}
// 其他可能的缓存操作...
}
public class CacheDemo {
public static void main(String[] args) {
SimpleCache<String, String> cache = new SimpleCache<>();
// 添加缓存项
cache.put("key1", "value1");
// 从缓存中读取项
String value = cache.get("key1");
System.out.println("Cached value for key1: " + value);
// 删除缓存项
cache.remove("key1");
}
}
在案例中,我们创建了一个名为SimpleCache的类,它使用ConcurrentHashMap来存储缓存项。这个简单的缓存系统是线程安全的,可以在多线程环境中使用,而不需要担心数据的一致性问题。
用来解决什么问题
ConcurrentHashMap通常用于解决需要高并发读写操作的场景,例如缓存系统、会话存储、共享资源的访问控制等。
示例代码:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public class SimpleConcurrentCache<K, V> {
private final ConcurrentHashMap<K, V> cacheMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<K, ReentrantLock> lockMap = new ConcurrentHashMap<>();
public V get(K key) {
return cacheMap.get(key);
}
public void put(K key, V value) {
cacheMap.put(key, value);
}
public void remove(K key) {
cacheMap.remove(key);
}
// 获取资源的访问锁
public ReentrantLock getLock(K key) {
// 使用 putIfAbsent 保证只有一个锁实例被创建
lockMap.putIfAbsent(key, new ReentrantLock());
return lockMap.get(key);
}
// 用于会话存储的方法
public void storeSession(K sessionId, V sessionData) {
put(sessionId, sessionData);
}
public V retrieveSession(K sessionId) {
return get(sessionId);
}
public void removeSession(K sessionId) {
remove(sessionId);
}
// 用于共享资源访问控制的方法
public void accessResource(K resourceId, Runnable accessLogic) {
ReentrantLock lock = getLock(resourceId);
lock.lock();
try {
// 执行访问资源的逻辑
accessLogic.run();
} finally {
lock.unlock();
}
}
}
public class CacheDemo {
public static void main(String[] args) {
SimpleConcurrentCache<String, String> cache = new SimpleConcurrentCache<>();
// 存储会话数据
cache.storeSession("user1_session", "User 1 Session Data");
// 访问会话数据
String sessionData = cache.retrieveSession("user1_session");
System.out.println("Retrieved session data: " + sessionData);
// 访问共享资源
cache.accessResource("shared_resource", () -> {
// 这里是访问共享资源的逻辑
System.out.println("Accessing shared resource");
});
// 移除会话数据
cache.removeSession("user1_session");
}
}
SimpleConcurrentCache
类使用ConcurrentHashMap
来存储缓存数据和锁对象。我们为每个资源创建了一个ReentrantLock
,以便在访问共享资源时提供同步控制。这样,我们可以确保在访问共享资源时,每次只有一个线程可以执行访问逻辑。
storeSession
、retrieveSession
和removeSession
方法提供了会话存储的基本操作。accessResource
方法允许线程安全地访问共享资源,它首先获取资源对应的锁,然后执行访问逻辑,并在最后释放锁。