首页 > 编程语言 >Java面试要点50 - List的线程安全实现:CopyOnWriteArrayList

Java面试要点50 - List的线程安全实现:CopyOnWriteArrayList

时间:2024-11-30 22:28:40浏览次数:7  
标签:Java int List 50 private final CopyOnWriteArrayList new public

在这里插入图片描述

文章目录


一、引入

在并发编程中,线程安全的集合类扮演着重要角色。CopyOnWriteArrayList作为List接口的线程安全实现,采用了一种独特的"写时复制"机制来保证线程安全。

二、实现原理解析

2.1 写时复制机制

CopyOnWriteArrayList的核心思想是在执行写操作时,先复制一个新的数组,在新数组上进行修改,最后将新数组替换旧数组。这种机制保证了读操作不需要加锁,从而大大提升了读操作的性能。

以下是其核心实现原理的示例代码:

public class CopyOnWriteArrayList<E> implements List<E> {
    private transient volatile Object[] array;
    
    final Object[] getArray() {
        return array;
    }
    
    final void setArray(Object[] a) {
        array = a;
    }
    
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
}

2.2 读写分离策略

CopyOnWriteArrayList在读操作时不需要加锁,因为内部数组array使用volatile修饰,保证了数组引用的可见性。写操作则通过ReentrantLock来保证同步。这种读写分离的策略使其特别适合读多写少的场景。

以下是其读写操作的核心实现:

public class CopyOnWriteArrayList<E> {
    // volatile修饰的数组引用,保证可见性
    private transient volatile Object[] array;
    
    // 用于写操作的锁
    final transient ReentrantLock lock = new ReentrantLock();
    
    // 读操作不需要加锁,直接返回数组元素
    public E get(int index) {
        return get(getArray(), index);
    }
    
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
    
    // 写操作需要加锁,并复制数组
    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock(); // 加锁保护写操作
        try {
            Object[] elements = getArray();
            E oldValue = get(elements, index);
            
            if (oldValue != element) {
                // 复制数组并修改指定位置的值
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock(); // 确保锁的释放
        }
    }
    
    // 迭代操作也不需要加锁,因为使用的是数组的快照
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    
    // 内部迭代器实现
    static final class COWIterator<E> implements Iterator<E> {
        private final Object[] snapshot;
        private int cursor;
        
        COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements; // 保存当前数组的快照
        }
        
        public boolean hasNext() {
            return cursor < snapshot.length;
        }
        
        @SuppressWarnings("unchecked")
        public E next() {
            if (!hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }
    }
}

这种实现方式使得读操作的性能非常高,因为完全不需要加锁,而写操作虽然会复制数组,但通过加锁机制保证了数据的一致性。

三、性能测试分析

通过一个实际的性能测试来了解CopyOnWriteArrayList在不同场景下的表现:

public class CopyOnWriteArrayListTest {
    private static final int THREAD_COUNT = 10;
    private static final int OPERATION_COUNT = 1000;
    
    public static void main(String[] args) throws InterruptedException {
        // 初始化测试数据
        List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
        List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
        
        // 测试并发写性能
        CountDownLatch writeLatch = new CountDownLatch(THREAD_COUNT);
        long writeStartTime = System.nanoTime();
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    copyOnWriteList.add("Item " + j);
                }
                writeLatch.countDown();
            }).start();
        }
        
        writeLatch.await();
        long writeTime = System.nanoTime() - writeStartTime;
        System.out.printf("CopyOnWriteArrayList写入耗时: %d ms%n", writeTime / 1_000_000);
        
        // 测试并发读性能
        CountDownLatch readLatch = new CountDownLatch(THREAD_COUNT);
        long readStartTime = System.nanoTime();
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    copyOnWriteList.get(j % copyOnWriteList.size());
                }
                readLatch.countDown();
            }).start();
        }
        
        readLatch.await();
        long readTime = System.nanoTime() - readStartTime;
        System.out.printf("CopyOnWriteArrayList读取耗时: %d ms%n", readTime / 1_000_000);
    }
}

四、应用场景分析

4.1 事件监听器管理

CopyOnWriteArrayList非常适合用于管理事件监听器列表,因为监听器的注册和移除操作相对较少,而事件触发时需要频繁遍历通知所有监听器:

public class EventManager {
    private final List<EventListener> listeners = new CopyOnWriteArrayList<>();
    
    public void addEventListener(EventListener listener) {
        listeners.add(listener);
    }
    
    public void removeEventListener(EventListener listener) {
        listeners.remove(listener);
    }
    
    public void fireEvent(Event event) {
        for (EventListener listener : listeners) {
            listener.onEvent(event);
        }
    }
}

4.2 缓存实现

在读多写少的缓存场景中,CopyOnWriteArrayList也能发挥其优势:

public class CacheManager<T> {
    private final List<T> cache = new CopyOnWriteArrayList<>();
    
    public void updateCache(List<T> newData) {
        cache.clear();
        cache.addAll(newData);
    }
    
    public List<T> getCache() {
        return new ArrayList<>(cache);  // 返回副本,避免外部修改
    }
    
    public T getCacheItem(int index) {
        return cache.get(index);
    }
}

五、最佳实践建议

5.1 性能优化技巧

在使用CopyOnWriteArrayList时,我们需要注意一些性能优化的关键点:

public class OptimizedListOperations {
    private final List<String> list = new CopyOnWriteArrayList<>();
    
    // 批量添加优化
    public void batchAdd(Collection<String> items) {
        // 一次性复制,而不是逐个添加
        List<String> currentList = new ArrayList<>(list);
        currentList.addAll(items);
        ((CopyOnWriteArrayList<String>)list).setArray(currentList.toArray());
    }
    
    // 迭代时的快照语义
    public void processItems() {
        // 利用快照特性,整个迭代过程使用同一个数组
        for (String item : list) {
            processItem(item);
        }
    }
    
    private void processItem(String item) {
        // 处理单个元素的业务逻辑
    }
}

5.2 常见陷阱规避

在使用CopyOnWriteArrayList时要注意规避一些常见的性能陷阱:

public class ListUsageGuide {
    private final CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    
    // 避免频繁修改操作
    public void wrongUsage() {
        // 错误示范:频繁修改导致性能问题
        for (int i = 0; i < 10000; i++) {
            list.add("item" + i);  // 每次add都会复制整个数组
        }
    }
    
    // 正确的使用方式
    public void correctUsage() {
        // 正确示范:批量操作
        ArrayList<String> temp = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            temp.add("item" + i);
        }
        list.addAll(temp);  // 只复制一次数组
    }
}

总结

CopyOnWriteArrayList通过其独特的写时复制机制,在保证线程安全的同时,为读多写少的并发场景提供了excellent的解决方案。它的核心优势在于读操作完全不需要加锁,从而能够提供更好的读性能。不过,这种实现机制也带来了写操作的开销,因为每次修改都需要复制整个数组。

在实际应用中,需要权衡使用场景的特点来决定是否使用CopyOnWriteArrayList。对于事件监听器列表、配置信息等读多写少的场景,CopyOnWriteArrayList是一个理想的选择。而对于写操作频繁的场景,可能需要考虑其他的线程安全集合实现。

标签:Java,int,List,50,private,final,CopyOnWriteArrayList,new,public
From: https://blog.csdn.net/weixin_55344375/article/details/144029309

相关文章

  • Java日志手机号脱敏工具类
    背景在开发过程中,很容易将用户敏感信息,例如手机号码、身份证等,打印在日志平台。为了保护用户数据,又不影响日志的打印,需要将日志中的敏感信息进行脱敏。效果没看明白,强烈建议pull项目,执行一下项目中SensitiveUtils#main方法。特性支持多层级Json/对象字段脱敏支持一次......
  • 【050】基于51单片机计步器【Keil程序+报告+原理图】
    ☆、设计硬件组成:51单片机最小系统+ADXL345三轴加速度传感器+LCD1602液晶显示+AT24C02存储芯片+按键控制。1、本设计采用STC89C51/52、AT89C51/52、AT89S51/52作为主控芯片,LCD1602实时显示;2、设计采用ADXL345三轴加速度传感器实现对行走步数的计数;3、系统能够计算出行走......
  • Java常见的锁策略
    目录Java常见的锁策略悲观锁和乐观锁轻量级锁和重量级锁自旋锁和挂起等待锁普通互斥锁和读写锁公平锁和非公平锁可重入锁和不可重入锁Java中的synchronized算哪种情况?系统原生的锁算哪种情况?synchronized的加锁过程,尤其是“自适应”是咋回事?synchronized中内置的优化策略......
  • ssm基于Java的高校教学业绩信息管理系统(10279)
     有需要的同学,源代码和配套文档领取,加文章最下方的名片哦一、项目演示项目演示视频二、资料介绍完整源代码(前后端源代码+SQL脚本)配套文档(LW+PPT+开题报告)远程调试控屏包运行三、技术介绍Java语言SSM框架SpringBoot框架Vue框架JSP页面Mysql数据库IDEA/Eclipse开发四、项......
  • Java 数据脱敏?别慌,掩护队已经上线!
    引言大家好!今天我们要聊一聊数据脱敏。这个词听起来像特工电影里的高科技武器,其实它就是给敏感数据穿上“伪装衣”,防止“坏人”偷窥。无论是银行账号、身份证号码、邮箱地址,这些信息都需要时刻保持低调。如何低调?没错——数据脱敏,Java已准备好为你服务!1.什么是数据脱敏?......
  • 从高校就业信息管理系统到Python和Java类系统开题报告:结构化研究的提纲优化
    个人名片......
  • 大学生HTML期末大作业——HTML+CSS+JavaScript培训机构(画室)
    HTML+CSS+JS【培训机构】网页设计期末课程大作业web前端开发技术web课程设计网页规划与设计......
  • JavaWeb:Servlet (学习笔记)【1】
    目录一,Servlet介绍1,简介2,Servlet技术特点3,Servlet在应用程序中的位置4,Servlet在程序中到底处于一个什么地位?二,servlet运行过程:三,servlet路径配置四,Servlet的生命周期1,伪单例模式2,生命周期的步骤3,讲解Servlet是一个伪单例模式五,什么是生命周期啊?就是说什么时候有......
  • 前端:JavaScript (学习笔记)【2】
    目录一,数组的使用1,数组的创建  [ ]2,数组的元素和长度3,数组的遍历方式4,数组的常用方法二,JavaScript中的对象1,常用对象(1)String和java中的String很类似,的常用方法(2) Number的常用属性和方法(包装类)   [1]属性   [2 ]数学方法(3)Math对象(4)Date......
  • Java Web : HTTP协议
    目录一,HTTP协议的概述二,HTTP协议的特点1,基于请求和响应模型2,简单快捷3,长链接:4,单向性:5,无状态6,灵活二,HTTP协议的交互流程1,请求部分【1】请求行(1个)(1)请求方式​编辑(2)请求地址【固定的】(3)协议(4)版本号【固定的】【2】请求头(n个)【3】请求主体2,响应部分【1】响应行......