18.2.6 集合线程安全解决方案

1. 线程安全问题概述

1.1 集合线程安全问题的根源

Java中的大多数集合类(如ArrayList、HashMap、HashSet等)都不是线程安全的,在多线程环境下使用会出现数据不一致、数据丢失、死循环等问题。

public class CollectionThreadSafetyDemo {
    
    // 演示ArrayList的线程安全问题
    public void demonstrateArrayListProblem() {
        List<Integer> list = new ArrayList<>();
        
        // 创建多个线程同时操作ArrayList
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        for (int i = 0; i < 10; i++) {
            final int threadId = i;
            executor.submit(() -> {
                for (int j = 0; j < 1000; j++) {
                    list.add(threadId * 1000 + j);
                }
            });
        }
        
        executor.shutdown();
        try {
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        // 期望大小:10 * 1000 = 10000
        // 实际大小:可能小于10000,存在数据丢失
        System.out.println("期望大小: 10000");
        System.out.println("实际大小: " + list.size());
    }
    
    // 演示HashMap的线程安全问题
    public void demonstrateHashMapProblem() {
        Map<Integer, String> map = new HashMap<>();
        
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        for (int i = 0; i < 10; i++) {
            final int threadId = i;
            executor.submit(() -> {
                for (int j = 0; j < 1000; j++) {
                    map.put(threadId * 1000 + j, "value" + (threadId * 1000 + j));
                }
            });
        }
        
        executor.shutdown();
        try {
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        System.out.println("HashMap期望大小: 10000");
        System.out.println("HashMap实际大小: " + map.size());
    }
}

1.2 常见的线程安全问题

数据竞争

多线程同时修改共享数据

数据不一致

数据丢失

并发写入时数据被覆盖

数据缺失

死循环

HashMap扩容时链表成环

程序卡死

索引越界

ArrayList扩容时的并发问题

异常抛出

内存可见性

线程间数据不可见

数据不同步

2. 同步包装器解决方案

2.1 Collections.synchronizedXxx()方法

public class SynchronizedCollectionsDemo {
    
    public void demonstrateSynchronizedCollections() {
        // 1. 同步List
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        
        // 2. 同步Set
        Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
        
        // 3. 同步Map
        Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
        
        // 基本操作是线程安全的
        syncList.add("item1");
        syncSet.add("element1");
        syncMap.put("key1", 1);
        
        System.out.println("同步集合创建完成");
    }
    
    // 同步集合的正确使用方式
    public void correctUsageOfSynchronizedCollections() {
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        
        // 错误:遍历时需要手动同步
        // for (String item : syncList) { // 可能抛出ConcurrentModificationException
        //     System.out.println(item);
        // }
        
        // 正确:遍历时手动同步
        synchronized (syncList) {
            for (String item : syncList) {
                System.out.println(item);
            }
        }
        
        // 正确:复合操作需要手动同步
        synchronized (syncList) {
            if (!syncList.contains("item")) {
                syncList.add("item");
            }
        }
    }
    
    // 同步集合性能测试
    public void testSynchronizedCollectionPerformance() {
        List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
        List<Integer> normalList = new ArrayList<>();
        
        int iterations = 1000000;
        
        // 测试同步List性能
        long startTime = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            syncList.add(i);
        }
        long syncTime = System.nanoTime() - startTime;
        
        // 测试普通List性能
        startTime = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            normalList.add(i);
        }
        long normalTime = System.nanoTime() - startTime;
        
        System.out.println("同步List时间: " + syncTime / 1000000 + "ms");
        System.out.println("普通List时间: " + normalTime / 1000000 + "ms");
        System.out.println("性能损失: " + (syncTime - normalTime) * 100 / normalTime + "%");
    }
}

2.2 同步包装器的局限性

public class SynchronizedCollectionLimitations {
    
    // 局限性1: 遍历不安全
    public void iterationProblem() {
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        
        // 添加一些数据
        for (int i = 0; i < 100; i++) {
            syncList.add("item" + i);
        }
        
        // 启动一个线程不断修改集合
        new Thread(() -> {
            for (int i = 100; i < 200; i++) {
                syncList.add("item" + i);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
        
        // 主线程遍历(可能抛出ConcurrentModificationException)
        try {
            for (String item : syncList) {
                System.out.println(item);
                Thread.sleep(1); // 模拟处理时间
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("遍历时发生并发修改异常");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    // 局限性2: 复合操作不安全
    public void compoundOperationProblem() {
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        
        // 错误的复合操作
        if (!syncList.contains("item")) { // 检查
            syncList.add("item");          // 添加
        }
        // 在检查和添加之间,其他线程可能已经添加了"item"
        
        // 正确的复合操作
        synchronized (syncList) {
            if (!syncList.contains("item")) {
                syncList.add("item");
            }
        }
    }
    
    // 局限性3: 性能问题
    public void performanceProblem() {
        List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
        
        // 所有操作都需要获取锁,即使是读操作
        // 在高并发场景下性能较差
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                for (int j = 0; j < 10000; j++) {
                    syncList.add(j);
                    int size = syncList.size(); // 读操作也需要锁
                }
            });
        }
        
        executor.shutdown();
    }
}

3. 并发集合解决方案

3.1 ConcurrentHashMap详解

public class ConcurrentHashMapDemo {
    
    public void demonstrateConcurrentHashMap() {
        // 创建ConcurrentHashMap
        ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
        
        // 基本操作
        concurrentMap.put("key1", 1);
        concurrentMap.put("key2", 2);
        
        // 原子操作
        concurrentMap.putIfAbsent("key3", 3);
        concurrentMap.replace("key1", 1, 10);
        
        // 批量操作
        Map<String, Integer> batch = new HashMap<>();
        batch.put("key4", 4);
        batch.put("key5", 5);
        concurrentMap.putAll(batch);
        
        System.out.println("ConcurrentHashMap: " + concurrentMap);
    }
    
    // ConcurrentHashMap高级操作
    public void advancedConcurrentHashMapOperations() {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        
        // 1. compute系列方法
        map.compute("counter", (key, value) -> {
            return value == null ? 1 : value + 1;
        });
        
        // 2. merge方法
        map.merge("sum", 10, Integer::sum);
        map.merge("sum", 20, Integer::sum); // 结果为30
        
        // 3. forEach并行操作
        map.put("a", 1);
        map.put("b", 2);
        map.put("c", 3);
        
        map.for

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务