Java多线程 -- JUC包源码分析2 -- Copy On Write/CopyOnWriteArrayList/CopyOnWriteArraySet

来源:互联网 发布:雍熙北伐 知乎 编辑:程序博客网 时间:2024/06/05 23:56

上1篇讲述了Java并发编程的第1个基本思想–CAS/乐观锁,这1篇接着讲述并发编程的第2个基本思想:CopyOnWrite 
CopyOnWrite基本思想 
CopyOnWriteArrayList 
CopyOnWriteArraySet 
AtomicReference的一个应用 
总结


CopyOnWrite基本思想

CopyOnWrite, 
顾名思义:就是在Write的时候,不直接Write源数据,而是把数据Copy一份出来改,改完之后,再通过悲观锁或者乐观锁的方式写回去。

那为什么不直接改,而是要拷贝一份改呢? 其实为了”读“的时候,不加锁! 
下面就通过几个案例来展现CopyOnWrite的应用。

CopyOnArrayList

和ArrayList一样,CopyOnArrayList的核心数据结构,也是一个数组,如下:

 private volatile transient Object[] array;
  • 1
  • 1

下面是CopyOnArrayList的几个”读“函数:

    final Object[] getArray() {        return array;    }    public E get(int index) {                    //没加锁        return (E)(getArray()[index]);    }    public boolean isEmpty() {                   //没加锁        return size() == 0;     }    public boolean contains(Object o) {          //没加锁        Object[] elements = getArray();        return indexOf(o, elements, 0, elements.length) >= 0;    }    public int indexOf(Object o) {               //没加锁        Object[] elements = getArray();        return indexOf(o, elements, 0, elements.length);    }        private static int indexOf(Object o, Object[] elements,                               int index, int fence) {        if (o == null) {            for (int i = index; i < fence; i++)                if (elements[i] == null)                    return i;        } else {            for (int i = index; i < fence; i++)                if (o.equals(elements[i]))                    return i;        }        return -1;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

这些”读“函数,都没有加锁,那如何保证”线程安全“的呢? 
答案就在”写“函数里面:

    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); //CopyOnWrite, 写的时候,先把之前的数组拷贝一份出来        newElements[len] = e;        setArray(newElements);  //把新数组,赋值给老数组        return true;    } finally {        lock.unlock();    }    }        final void setArray(Object[] a) {        array = a;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

其他“写”函数,比如remove, 和add类似,在此不在详述。

CopyOnArraySet

就是用Array实现的一个Set,保证所有元素都不重复。其内部就是封装的一个CopyOnArrayList

public class CopyOnWriteArraySet<E> extends AbstractSet<E>        implements java.io.Serializable {    private static final long serialVersionUID = 5457747651344034263L;    private final CopyOnWriteArrayList<E> al;   //封装的CopyOnWriteArrayList    /**     * Creates an empty set.     */    public CopyOnWriteArraySet() {        al = new CopyOnWriteArrayList<E>();    }    public boolean add(E e) {       return al.addIfAbsent(e);   //不重复的,加进去    }    ...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

AtomicReference的一个应用

问题的提出:

一个NumberRange的对象,如下所示: 
public class NumberRange 

private int lower; 
private int upper;

public int getRange() 

return upper - lower; 

在多线程情况下,如何不加锁,实现该对象的线程安全?

错误的方法1:使用AtomicInteger

public class NumberRange {    // INVARIANT: lower <= upper    private final AtomicInteger lower = new AtomicInteger(0);    private final AtomicInteger upper = new AtomicInteger(0);    public void setLower(int i) {        // Warning -- unsafe check-then-act        if (i > upper.get())              throw new IllegalArgumentException(                    "can't set lower to " + i + " > upper");        lower.set(i);        //原子操作    }    public void setUpper(int i) {        // Warning -- unsafe check-then-act        if (i < lower.get())            throw new IllegalArgumentException(                    "can't set upper to " + i + " < lower");        upper.set(i);       //原子操作    }    public boolean isInRange(int i) {        return (i >= lower.get() && i <= upper.get());    }    }上述的setLower(),  setUpper()各自都是原子的,但是2者合在一起,并不是原子的。也就是说,多个线程分别调用这2个函数的时候,并不能实现互斥访问。这将导致lower的值,可能大于upper的值。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

正确的方法:final + CopyOnWrite + 乐观锁

public class CasNumberRange {    //Immutable    private static class IntPair {        final int lower;  //1. final类型,只能copy on write,不能直接更改        final int upper;        ...    }    private final AtomicReference<IntPair> values =                       new AtomicReference<IntPair>(new IntPair(0, 0));    public int getLower() { return values.get().lower; }    public int getUpper() { return values.get().upper; }    public void setLower(int i) {        while (true) {            IntPair oldv = values.get();            if (i > oldv.upper)                throw new IllegalArgumentException(                   "Can't set lower to " + i + " > upper");            IntPair newv = new IntPair(i, oldv.upper);   //2. CopyOnWrite            if (values.compareAndSet(oldv, newv))   //3. CAS乐观锁                return;        }    }    // similarly for setUpper}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

备注:把此问题推而广之,当我们想实现”不加锁,对一个大的对象实现线程安全访问时“, 就可利用CopyOnWrite + CAS乐观锁来达到同样的效果。

总结

CopyOnWrite的主要目的是实现“写”加锁,“读”不加锁,从而提高并发度。在上面例子中,CopyOnWriteArrayList使用了CopyOnWrite + 悲观锁; NumberRange使用了CopyOnWrite + 乐观锁。

阅读全文
1 0