Java ConcurrentHashMap语法知识点及案例代码
ConcurrentHashMap
是 Java 中一个非常重要的线程安全的哈希表实现,它允许并发访问和修改,并且性能相对较好。以下是 ConcurrentHashMap
的语法知识点以及一个带有详细注释的示例代码。
语法知识点
-
引入包:
import java.util.concurrent.ConcurrentHashMap;
-
创建对象:
ConcurrentHashMap<KeyType, ValueType> map = new ConcurrentHashMap<>();
-
基本方法:
put(K key, V value)
:将一个键值对放入映射中。get(Object key)
:根据键获取值。remove(Object key)
:根据键移除键值对。containsKey(Object key)
:检查映射中是否包含指定的键。size()
:返回映射中的键值对数量。isEmpty()
:检查映射是否为空。forEach(BiConsumer<? super K, ? super V> action)
:对映射中的每个键值对执行给定的操作。
-
并发特性:
ConcurrentHashMap
使用分段锁(Segment Locks)或更现代的 CAS(Compare-And-Swap)操作来实现并发性,从而允许多个线程同时读取和写入。- 它的内部实现复杂,但对外提供了简洁的接口。
示例代码
以下是一个简单的示例代码,展示了如何使用 ConcurrentHashMap
来存储和管理线程安全的键值对。
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
// 创建一个ConcurrentHashMap实例
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 使用put方法添加元素
map.put("Apple", 1);
map.put("Banana", 2);
map.put("Orange", 3);
// 使用get方法获取元素
Integer value = map.get("Banana");
System.out.println("Value for 'Banana': " + value);
// 使用containsKey方法检查是否包含某个键
boolean containsKey = map.containsKey("Apple");
System.out.println("Map contains 'Apple': " + containsKey);
// 使用remove方法移除元素
map.remove("Orange");
// 使用size方法获取元素数量
int size = map.size();
System.out.println("Size of the map: " + size);
// 使用isEmpty方法检查是否为空
boolean isEmpty = map.isEmpty();
System.out.println("Is the map empty? " + isEmpty);
// 使用forEach方法遍历所有元素
System.out.println("Elements in the map:");
map.forEach((key, value) -> {
System.out.println("Key: " + key + ", Value: " + value);
});
// 模拟并发访问
Runnable task = () -> {
for (int i = 0; i < 10; i++) {
String key = "Key" + i;
map.put(key, i);
System.out.println(Thread.currentThread().getName() + " added " + key + " with value " + i);
}
};
// 创建并启动多个线程
Thread thread1 = new Thread(task, "Thread-1");
Thread thread2 = new Thread(task, "Thread-2");
thread1.start();
thread2.start();
try {
// 等待线程结束
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 最终的元素数量(可能大于初始数量,因为多线程添加)
System.out.println("Final size of the map after concurrent operations: " + map.size());
}
}
代码解释
-
创建
ConcurrentHashMap
实例:ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
-
添加元素:
map.put("Apple", 1); map.put("Banana", 2); map.put("Orange", 3);
-
获取元素:
Integer value = map.get("Banana");
-
检查是否包含某个键:
boolean containsKey = map.containsKey("Apple");
-
移除元素:
map.remove("Orange");
-
获取元素数量:
int size = map.size();
-
检查是否为空:
boolean isEmpty = map.isEmpty();
-
遍历所有元素:
map.forEach((key, value) -> { System.out.println("Key: " + key + ", Value: " + value); });
-
模拟并发访问:
- 定义了一个
Runnable
任务,每个任务会向ConcurrentHashMap
中添加一些键值对。 - 创建并启动两个线程来执行这个任务。
- 使用
join
方法等待两个线程执行完毕。
- 定义了一个
-
输出最终的元素数量:
System.out.println("Final size of the map after concurrent operations: " + map.size());
通过这些示例和解释,你应该能够掌握 ConcurrentHashMap
的基本用法和并发特性。希望这些信息对你有帮助!
以下是关于 ConcurrentHashMap
的几个具体案例,这些案例展示了在不同场景下如何使用 ConcurrentHashMap
来实现线程安全的键值对存储和访问。
案例一:用户访问计数器
在这个案例中,我们使用 ConcurrentHashMap
来实现一个用户访问计数器,用于统计不同用户的访问次数。
import java.util.concurrent.ConcurrentHashMap;
public class UserVisitCounter {
// 使用ConcurrentHashMap存储用户访问次数
private ConcurrentHashMap<String, Integer> userCounts = new ConcurrentHashMap<>();
// 用户访问时调用此方法增加计数
public void visit(String userId) {
// 使用compute方法实现线程安全的计数更新
userCounts.compute(userId, (key, value) -> (value == null) ? 1 : value + 1);
}
// 获取用户的访问次数
public int getCount(String userId) {
// 使用getOrDefault方法获取访问次数,如果未找到则返回0
return userCounts.getOrDefault(userId, 0);
}
public static void main(String[] args) {
UserVisitCounter counter = new UserVisitCounter();
// 模拟多线程用户访问
Runnable task = () -> {
for (int i = 0; i < 10; i++) {
String userId = "User" + (int) (Math.random() * 100);
counter.visit(userId);
System.out.println(Thread.currentThread().getName() + " visited " + userId + ", total count: " + counter.getCount(userId));
}
};
// 创建并启动多个线程
for (int i = 0; i < 5; i++) {
new Thread(task).start();
}
}
}
案例二:缓存系统
在这个案例中,我们使用 ConcurrentHashMap
来实现一个简单的缓存系统,用于存储和访问缓存数据。
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class SimpleCache<K, V> {
// 使用ConcurrentHashMap存储缓存数据
private ConcurrentHashMap<K, CacheEntry<K, V>> cache = new ConcurrentHashMap<>();
// 缓存项,包含值和过期时间
private static class CacheEntry<K, V> {
final V value;
final long expireTime;
CacheEntry(V value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}
boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}
// 添加缓存项,指定过期时间
public void put(K key, V value, long expireTime, TimeUnit timeUnit) {
long expireTimeMillis = System.currentTimeMillis() + timeUnit.toMillis(expireTime);
cache.put(key, new CacheEntry<>(value, expireTimeMillis));
}
// 获取缓存项的值,如果缓存项已过期,则返回null
public V get(K key) {
CacheEntry<K, V> entry = cache.get(key);
if (entry != null && !entry.isExpired()) {
return entry.value;
} else {
// 缓存项已过期或不存在,从缓存中移除(可选)
cache.remove(key);
return null;
}
}
public static void main(String[] args) throws InterruptedException {
SimpleCache<String, String> cache = new SimpleCache<>();
// 添加缓存项,设置过期时间为5秒
cache.put("key1", "value1", 5, TimeUnit.SECONDS);
// 访问缓存项
System.out.println("key1: " + cache.get("key1")); // 输出: value1
// 等待6秒,使缓存项过期
Thread.sleep(6000);
// 再次访问缓存项,此时应返回null
System.out.println("key1 after expiration: " + cache.get("key1")); // 输出: null
}
}
案例三:任务分配系统
在这个案例中,我们使用 ConcurrentHashMap
来实现一个简单的任务分配系统,用于将任务分配给多个工作线程。
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TaskDistributor {
// 使用ConcurrentHashMap存储任务和对应的处理线程
private ConcurrentHashMap<String, String> taskAssignments = new ConcurrentHashMap<>();
// 添加任务并分配处理线程
public void assignTask(String taskId, String workerId) {
taskAssignments.put(taskId, workerId);
}
// 获取任务的处理线程
public String getWorkerForTask(String taskId) {
return taskAssignments.get(taskId);
}
public static void main(String[] args) {
TaskDistributor distributor = new TaskDistributor();
// 创建工作线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 模拟添加任务并分配处理线程
for (int i = 0; i < 10; i++) {
String taskId = "Task" + i;
String workerId = "Worker" + (i % 3); // 简单地使用取模运算分配工作线程
distributor.assignTask(taskId, workerId);
}
// 工作线程执行任务
Runnable taskExecution = () -> {
for (String taskId : distributor.taskAssignments.keySet()) {
String workerId = distributor.getWorkerForTask(taskId);
if (workerId.equals(Thread.currentThread().getName().substring(7))) {
System.out.println(Thread.currentThread().getName() + " is processing " + taskId);
}
}
};
// 启动工作线程
for (int i = 0; i < 3; i++) {
executorService.execute(new Thread(taskExecution, "Worker" + i));
}
// 关闭线程池(等待所有任务完成)
executorService.shutdown();
}
}
注意:在第三个案例中,由于线程池和任务分配是并发进行的,因此实际的任务分配和处理可能会有所不同。这里的示例主要是为了展示如何使用 ConcurrentHashMap
来存储和访问任务分配信息。在实际应用中,可能需要更复杂的逻辑来处理任务分配和线程同步。
这些案例展示了 ConcurrentHashMap
在不同场景下的应用,包括用户访问计数器、缓存系统和任务分配系统。通过使用 ConcurrentHashMap
,我们可以实现线程安全的键值对存储和访问,从而满足高并发场景下的需求。