zoukankan      html  css  js  c++  java
  • 锁 和 CopyOnWrite的实现

    1.普通锁

    只有lock功能, Java实现:ReentrantLock lock = new ReentrantLock();

    • lock和unlock:
    lock.lock();
    lock.unlock();

    2.读写锁

    读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥。

    总之,读的时候上读锁,写的时候上写锁! Java里面的实现:ReentrantReadWriteLock

    ReadWriteLock mylock = new ReentrantReadWriteLock(false);
    • 读锁lock、unlock:
    myLock.readLock().lock();
    myLock.readLock().unlock();
    • 写锁lock、unlock:
    myLock.writeLock().lock();
    myLock.writeLock().unlock();

    3.总结

    读写锁的本意是分别对读写状态进行互斥区分,有互斥时才加锁,否则放行.

    互斥的情况有:

    1. 读写互斥.
    2. 写写互斥.

    不互斥的情况是:读读,

    读写锁使用更加高效,尤其适用于读线程多于写线程的场景。

    4.CopyOnWrite

    • 使用场景:并发少量的写,多个读(确实适合Kafka的应用场景)。
    • JDK源码有CopyOnWriteArrayList和 CopyOnWriteArraySet的实现
    • Kafka的CopyOnWriteMap是自己实现的,基于lock的;另外还有基于CAS实现的,比如com.sun.jersey.client.impl.CopyOnWriteHashMap,具体实现暂且不讨论。

    CopyOnWriteArrayList的核心思想是利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。

    Kafka-0.10的队列RecordAccumulator使用了自实现的CopyOnWriteMap.关键点如下:

    • map声明: volatile
    • read操作不加锁
    • put操作加锁:synchronized

    A simple read-optimized map implementation that synchronizes only writes and does a full copy on each modification

    private volatile Map<K, V> map; // volatile
    this.map = Collections.unmodifiableMap(map); // 不可变map
    
    public V get(Object k) { // read操作不加锁
            return map.get(k);
        }
        
    // put操作加锁,先复制,在put.
    public synchronized V put(K k, V v) {
            Map<K, V> copy = new HashMap<K, V>(this.map);
            V prev = copy.put(k, v);
            this.map = Collections.unmodifiableMap(copy);
            return prev;
        }

    5. CopyOnWriteArrayList - 写时复制

    JDK里的CopyOnWriteArrayList,核心是任何时候,保障只有一个写者。因此,也是对读不加锁、对写进行加锁。

    基本思路是,从共享同一个内容,当想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。

    • 底层实现是数组Object[],声明:volatile
    • 加锁的方式是ReentrantLock
    • copy的方法是Arrays.copyOf

    5.1 add 加锁

    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();
            }
        }

    5.2 读不加锁

    读的时候不需要加锁,如果读的时候有多个线程正在向 CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的 CopyOnWriteArrayList

    public E get(int index) {
            return get(getArray(), index);
        }

    很简单,只要了解了CopyOnWrite机制,我们可以实现各种CopyOnWrite容器,并且在不同的应用场景中使用。

    5.3 CopyOnWrite的应用场景

    CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。

    注意
    1. 减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。
    2. 使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。

    5.4 缺点

    • 内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。
    • 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
    • 从上面那点,我想到另外一个知识点:volatile保证的是实时一致性。对比来看,CopyOnWriteArrayList的底层结构未使用volatile关键字;CopyOnWriteMap使用了volatile,能够对map的引用保证实时一致性,可以理解成对数据也是实时一致性的。

  • 相关阅读:
    Oracle Core 学习笔记二 Transactions 和 Consistency 说明
    Oracle AUTO_SPACE_ADVISOR_JOB 说明
    Windows 下 ftp 上传文件 脚本
    Oracle 11g 中 Direct path reads 特性 说明
    Linux 使用 wget 下载 Oracle 软件说明
    Oracle 10g read by other session 等待 说明
    Oracle 11g RAC INS06006 Passwordless SSH connectivity not set up between the following node(s) 解决方法
    SecureCRT 工具 上传下载数据 与 ASCII、Xmodem、Ymodem 、Zmodem 说明
    Oracle RAC root.sh 报错 Timed out waiting for the CRS stack to start 解决方法
    Oracle RESETLOGS 和 NORESETLOGS 区别说明
  • 原文地址:https://www.cnblogs.com/byrhuangqiang/p/6338098.html
Copyright © 2011-2022 走看看