Bài 7: Concurrent Collections
I. Giới Thiệu
Trong môi trường đa luồng, việc sử dụng các collection tiêu chuẩn như ArrayList, HashMap, và HashSet có thể dẫn đến các vấn đề về đồng bộ hóa và an toàn luồng (thread safety). Java cung cấp các collection đồng bộ hóa (concurrent collections) trong gói java.util.concurrent để giải quyết vấn đề này. Các collection này được thiết kế để hỗ trợ truy cập đồng thời từ nhiều luồng mà không cần phải tự quản lý đồng bộ hóa.
II. Các Concurrent Collections Phổ Biến
ConcurrentHashMap
Đặc điểm:
Là phiên bản đồng bộ hóa của
HashMap, hỗ trợ truy cập đồng thời hiệu quả.Sử dụng phân đoạn (segmentation) để chia nhỏ bảng băm thành nhiều phần và đồng bộ hóa trên mỗi phần.
Khi nào sử dụng: Khi cần một
Mapđồng bộ và hiệu suất cao trong môi trường đa luồng.Ví dụ:
import java.util.concurrent.ConcurrentHashMap; import java.util.Map; public class ConcurrentHashMapExample { public static void main(String[] args) { Map<String, Integer> concurrentMap = new ConcurrentHashMap<>(); concurrentMap.put("Apple", 1); concurrentMap.put("Banana", 2); // Truy xuất giá trị int appleCount = concurrentMap.get("Apple"); System.out.println("Apple count: " + appleCount); // Output: Apple count: 1 // Thêm giá trị mới concurrentMap.put("Orange", 3); // In toàn bộ Map concurrentMap.forEach((key, value) -> { System.out.println(key + ": " + value); }); // Output: // Apple: 1 // Banana: 2 // Orange: 3 } }
CopyOnWriteArrayList
Đặc điểm:
Là phiên bản đồng bộ hóa của
ArrayList, mỗi khi có thay đổi (thêm/xóa), một bản sao của danh sách được tạo ra.Hiệu suất cao khi thao tác đọc nhiều hơn ghi.
Khi nào sử dụng: Khi cần một danh sách đồng bộ và chủ yếu là thao tác đọc trong môi trường đa luồng.
Ví dụ:
import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class CopyOnWriteArrayListExample { public static void main(String[] args) { List<String> list = new CopyOnWriteArrayList<>(); list.add("Apple"); list.add("Banana"); // Đọc danh sách list.forEach(System.out::println); // Output: Apple, Banana // Thêm phần tử mới list.add("Orange"); // In lại danh sách list.forEach(System.out::println); // Output: Apple, Banana, Orange } }
CopyOnWriteArraySet
Đặc điểm:
Là phiên bản đồng bộ hóa của
HashSet, mỗi khi có thay đổi, một bản sao của tập hợp được tạo ra.Hiệu suất cao khi thao tác đọc nhiều hơn ghi.
Khi nào sử dụng: Khi cần một tập hợp đồng bộ và chủ yếu là thao tác đọc trong môi trường đa luồng.
Ví dụ:
import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; public class CopyOnWriteArraySetExample { public static void main(String[] args) { Set<String> set = new CopyOnWriteArraySet<>(); set.add("Apple"); set.add("Banana"); // Đọc tập hợp set.forEach(System.out::println); // Output: Apple, Banana // Thêm phần tử mới set.add("Orange"); // In lại tập hợp set.forEach(System.out::println); // Output: Apple, Banana, Orange } }
BlockingQueue
Đặc điểm:
Là một interface cung cấp các phương thức để thêm và lấy phần tử từ hàng đợi theo cách đồng bộ.
Các lớp triển khai phổ biến:
ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue.
Khi nào sử dụng: Khi cần một hàng đợi đồng bộ với khả năng chặn khi hàng đợi đầy hoặc rỗng.
Ví dụ:
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class BlockingQueueExample { public static void main(String[] args) throws InterruptedException { BlockingQueue<String> queue = new ArrayBlockingQueue<>(3); queue.put("Apple"); queue.put("Banana"); queue.put("Orange"); // Lấy phần tử khỏi hàng đợi System.out.println(queue.take()); // Output: Apple System.out.println(queue.take()); // Output: Banana System.out.println(queue.take()); // Output: Orange } }
ConcurrentSkipListMap
Đặc điểm:
Là phiên bản đồng bộ hóa của
TreeMap, hỗ trợ truy cập đồng thời và duy trì thứ tự sắp xếp.Dựa trên cấu trúc dữ liệu Skip List.
Khi nào sử dụng: Khi cần một
Mapđồng bộ và duy trì thứ tự sắp xếp trong môi trường đa luồng.Ví dụ:
import java.util.concurrent.ConcurrentSkipListMap; import java.util.Map; public class ConcurrentSkipListMapExample { public static void main(String[] args) { Map<String, Integer> skipListMap = new ConcurrentSkipListMap<>(); skipListMap.put("Apple", 1); skipListMap.put("Banana", 2); // Thêm giá trị mới skipListMap.put("Orange", 3); // In toàn bộ Map skipListMap.forEach((key, value) -> { System.out.println(key + ": " + value); }); // Output: // Apple: 1 // Banana: 2 // Orange: 3 } }
III. So Sánh Hiệu Năng Và Trường Hợp Sử Dụng
| Lớp | Thứ Tự Phần Tử | Hiệu Suất Thêm/Xóa | Hiệu Suất Tìm Kiếm | Đồng Bộ | Khi Nào Sử Dụng |
| ConcurrentHashMap | Không | Rất nhanh (O(1)) | Rất nhanh (O(1)) | Có | Khi cần đồng bộ và hiệu suất cao |
| CopyOnWriteArrayList | Theo thứ tự chèn | Nhanh khi đọc | Nhanh khi đọc | Có | Khi cần đồng bộ và chủ yếu là thao tác đọc |
| CopyOnWriteArraySet | Theo thứ tự chèn | Nhanh khi đọc | Nhanh khi đọc | Có | Khi cần đồng bộ và chủ yếu là thao tác đọc |
| BlockingQueue | Không | Tùy thuộc vào triển khai | Tùy thuộc vào triển khai | Có | Khi cần hàng đợi đồng bộ với khả năng chặn |
| ConcurrentSkipListMap | Theo thứ tự sắp xếp | Trung bình (O(log n)) | Trung bình (O(log n)) | Có | Khi cần đồng bộ và duy trì thứ tự sắp xếp |
IV. Ví Dụ Minh Họa Chi Tiết
Sử Dụng ConcurrentHashMap trong Môi Trường Đa Luồng
Mô tả: Quản lý số lượng sản phẩm trong kho hàng.
import java.util.concurrent.ConcurrentHashMap; import java.util.Map; public class Warehouse { private final Map<String, Integer> stock = new ConcurrentHashMap<>(); public void addProduct(String product, int quantity) { stock.merge(product, quantity, Integer::sum); } public int getProductQuantity(String product) { return stock.getOrDefault(product, 0); } public static void main(String[] args) { Warehouse warehouse = new Warehouse(); warehouse.addProduct("Laptop", 10); warehouse.addProduct("Smartphone", 5); System.out.println("Laptop quantity: " + warehouse.getProductQuantity("Laptop")); // Output: Laptop quantity: 10 System.out.println("Smartphone quantity: " + warehouse.getProductQuantity("Smartphone")); // Output: Smartphone quantity: 5 } }Sử Dụng CopyOnWriteArrayList để Quản Lý Danh Sách Người Dùng
Mô tả: Quản lý danh sách người dùng trực tuyến trong ứng dụng chat.
import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class ChatRoom { private final List<String> onlineUsers = new CopyOnWriteArrayList<>(); public void addUser(String user) { onlineUsers.add(user); System.out.println(user + " has joined the chat."); } public
void removeUser(String user) { onlineUsers.remove(user); System.out.println(user + " has left the chat."); }
public void listUsers() { System.out.println("Online users: " + onlineUsers); }
public static void main(String[] args) { ChatRoom chatRoom = new ChatRoom(); chatRoom.addUser("Alice"); chatRoom.addUser("Bob"); chatRoom.listUsers(); // Output: Online users: [Alice, Bob]
chatRoom.removeUser("Alice"); chatRoom.listUsers(); // Output: Online users: [Bob] } }
3. **Sử Dụng BlockingQueue để Thực Hiện Mô Hình Producer-Consumer**
**Mô tả:** Mô hình Producer-Consumer sử dụng `BlockingQueue`.
```java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i++) {
System.out.println("Produced: " + i);
queue.put(i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
Integer item = queue.take();
System.out.println("Consumed: " + item);
Thread.sleep(200);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
Thread producerThread = new Thread(new Producer(queue));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
Sử Dụng ConcurrentSkipListMap để Quản Lý Bảng Xếp Hạng
Mô tả: Quản lý bảng xếp hạng người chơi trong trò chơi.
import java.util.concurrent.ConcurrentSkipListMap; import java.util.Map; public class Leaderboard { private final Map<String, Integer> scores = new ConcurrentSkipListMap<>(); public void addScore(String player, int score) { scores.put(player, score); } public void printLeaderboard() { scores.forEach((player, score) -> { System.out.println(player + ": " + score); }); } public static void main(String[] args) { Leaderboard leaderboard = new Leaderboard(); leaderboard.addScore("Alice", 100); leaderboard.addScore("Bob", 200); leaderboard.addScore("Charlie", 150); leaderboard.printLeaderboard(); // Output: // Alice: 100 // Bob: 200 // Charlie: 150 } }
V. Bài Tập Thực Hành
Tạo chương trình Java sử dụng ConcurrentHashMap để quản lý số lượng sản phẩm trong cửa hàng.
Yêu cầu: Thêm, cập nhật và truy xuất số lượng sản phẩm một cách đồng bộ trong môi trường đa luồng.
Ví dụ:
import java.util.concurrent.ConcurrentHashMap; import java.util.Map; public class StoreInventory { private final Map<String, Integer> inventory = new ConcurrentHashMap<>(); public void addProduct(String product, int quantity) { inventory.merge(product, quantity, Integer::sum); } public int getProductQuantity(String product) { return inventory.getOrDefault(product, 0); } public static void main(String[] args) { StoreInventory store = new StoreInventory(); store.addProduct("Laptop", 10); store.addProduct("Smartphone", 5); System.out.println("Laptop quantity: " + store.getProductQuantity("Laptop")); // Output: Laptop quantity: 10 System.out.println("Smartphone quantity: " + store.getProductQuantity("Smartphone")); // Output: Smartphone quantity: 5 } }
Tạo chương trình Java sử dụng CopyOnWriteArrayList để quản lý danh sách các sự kiện trong ứng dụng.
Yêu cầu: Thêm, xóa và liệt kê các sự kiện một cách đồng bộ trong môi trường đa luồng.
Ví dụ:
import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class EventManager { private final List<String> events = new CopyOnWriteArrayList<>(); public void addEvent(String event) { events.add(event); System.out.println("Event added: " + event); } public void removeEvent(String event) { events.remove(event); System.out.println("Event removed: " + event); } public void listEvents() { System.out.println("Events: " + events); } public static void main(String[] args) { EventManager eventManager = new EventManager(); eventManager.addEvent("Conference"); eventManager.addEvent("Workshop"); eventManager.listEvents(); // Output: Events: [Conference, Workshop] eventManager.removeEvent("Conference"); eventManager.listEvents(); // Output: Events: [Workshop] } }
Tạo chương trình Java sử dụng BlockingQueue để thực hiện mô hình Producer-Consumer cho xử lý đơn hàng.
Yêu cầu: Mô hình Producer-Consumer với Producer thêm đơn hàng và Consumer xử lý đơn hàng.
Ví dụ:
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; class OrderProducer implements Runnable { private final BlockingQueue<String> queue; OrderProducer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { try { for (int i = 1; i <= 10; i++) { String order = "Order" + i; System.out.println("Produced: " + order); queue.put(order); Thread.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } class OrderConsumer implements Runnable { private final BlockingQueue<String> queue; OrderConsumer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { try { while (true) { String order = queue.take(); System.out.println("Consumed: " + order); Thread.sleep(200); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public class OrderProcessingExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(5); Thread producerThread = new Thread(new OrderProducer(queue)); Thread consumerThread = new Thread(new OrderConsumer(queue)); producerThread.start(); consumerThread.start(); } }
Tạo chương trình Java sử dụng ConcurrentSkipListMap để quản lý danh sách khách hàng và số điểm thưởng của họ.
Yêu cầu: Thêm, cập nhật và liệt kê danh sách khách hàng cùng số điểm thưởng một cách đồng bộ trong môi trường đa luồng.
Ví dụ:
import java.util.concurrent.ConcurrentSkipListMap; import java.util.Map; public class CustomerRewards { private final Map<String, Integer> rewards = new ConcurrentSkipListMap<>(); public void addReward(String customer, int points) { rewards.put(customer, points); } public void updateReward(String customer, int points) { rewards.merge(customer, points, Integer::sum); } public void listRewards() { rewards.forEach((customer, points) -> { System.out.println(customer + ": " + points); }); } public static void main(String[] args) { CustomerRewards customerRewards = new CustomerRewards(); customerRewards.addReward("Alice", 100); customerRewards.addReward("Bob", 200); customerRewards.updateReward("Alice", 50); customerRewards.listRewards(); // Output: // Alice: 150 // Bob: 200 } }
VI. Kết Luận
Trong bài viết này, chúng ta đã khám phá các Concurrent Collections trong Java và cách sử dụng chúng trong môi trường đa luồng. Hiểu rõ và sử dụng hiệu quả các Concurrent Collections sẽ giúp bạn xây dựng các ứng dụng an toàn và hiệu quả trong môi trường đa luồng. Các ví dụ minh họa và bài tập thực hành sẽ giúp củng cố kiến thức và ứng dụng hiệu quả trong các dự án thực tế.
VII. Tài Liệu Tham Khảo
Java SE Documentation: Java Concurrent Collections
"Java Concurrency in Practice" by Brian Goetz: Một tài liệu tuyệt vời về lập trình đa luồng và các concurrent collections trong Java.