JDK源碼——java.util.concurrent(七)

CopyOnWriteArrayList、CopyOnWriteArraySet

這兩個類都比較簡單內部有一個數組和一把鎖,對所有寫操作加鎖.每次進行寫操作時都複製一個新的數組,在新數組上進行;而讀則在老數組上進行,有讀寫分離的意思,比Vector效率高,適合都多寫少的情況.
咱們看看其如何實現的

    transient final ReentrantLock lock = new ReentrantLock();
    private volatile transient Object[] array;
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    final void setArray(Object[] a) {
        array = a;
    }

先看看其讀取方法

    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;
    }
    private static int lastIndexOf(Object o, Object[] elements, int index) {
        if (o == null) {
            for (int i = index; i >= 0; i--)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i >= 0; i--)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }
    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);
    }
    public int lastIndexOf(Object o) {
        Object[] elements = getArray();
        return lastIndexOf(o, elements, elements.length - 1);
    }

這些讀的方法都很簡單,也沒什麼新意.下面看看寫操作

//指定位置插入元素
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);
            } else {
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
    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();
        }
    }
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

可以看到所有的寫操作都加了鎖且都是操作新數組,這樣能有效避免寫操作之間的影響,且能避免進行寫操作時讀操作不準確;但這樣加大了內存的消耗,因此適合讀多寫少的場景.

CopyOnWriteArraySet內部持有一個CopyOnWriteArrayList引用,所有操作都是基於對CopyOnWriteArrayList的操作.


ArrayBlockingQueue

首先咱們先來看看其用法:

public class ArrayBlockingQueueTest {
    public static void main(String[] args) {
        final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue(3);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    try {
                        queue.put("a");
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        new Thread(runnable).start();


        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    try {
                        String take = queue.take();
                        System.out.println(take);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        new Thread(runnable2).start();
    }
}

ArrayBlockingQueue阻塞有界隊列,具有一下幾個基本特點:

  1. 基於數組實現的阻塞有界FIFO隊列
  2. 基於數組創建因此隊列大小不能改變;當隊列滿時添加元素方法會被阻塞,當隊列空時獲取元素方法會被阻塞
  3. 提供公平模式、非公平模式

下面咱們來看看其源碼,先看其主要屬性與構造方法

 //存放元素的數組
 final Object[] items;
 //take, poll, peek or remove等方法操作的元素位置
 int takeIndex;
 //put, offer, or add等方法操作的元素位置
 int putIndex;
 //隊列中元素數量
 int count;
 //ReentrantLock控制併發
 final ReentrantLock lock;
 //取出元素方法等待條件
 private final Condition notEmpty;
 //添加元素方法等待條件
 private final Condition notFull;
     //默認非公平鎖
     public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

下面看看其主要方法,先看add()與offer()

    //add()實際上是調用offer()完成的
    public boolean add(E e) {
        return super.add(e);
    }
    //可以看出,此方法實際上是不阻塞的
    public boolean offer(E e) {
        //檢查元素是否爲Null,如果爲null直接拋出異常
        checkNotNull(e);
        //加鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //conut==items.length時說明隊列已滿
            if (count == items.length)
                return false;
            else {
                insert(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        //檢查元素
        checkNotNull(e);
        //
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        //響應中斷
        lock.lockInterruptibly();
        try {
            //如果隊列滿了且等待時間小於等於0,則返回false;否則等待指定時間
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            insert(e);
            return true;
        } finally {
            lock.unlock();
        }
    }
    //插入方法
    private void insert(E x) {
        items[putIndex] = x;
        putIndex = inc(putIndex);//put元素位置
        ++count;
        //通知lock的非空條件隊列上的線程
        notEmpty.signal();
    }
    final int inc(int i) {
        return (++i == items.length) ? 0 : i;
    }

再看看put()和take()

    public void put(E e) throws InterruptedException {
        //檢查元素
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果隊列滿了則阻塞,等待隊列不滿 
            while (count == items.length)
                notFull.await();
            insert(e);
        } finally {
            lock.unlock();
        }
    }
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果隊列爲空,則等待
            while (count == 0)
                notEmpty.await();
            return extract();
        } finally {
            lock.unlock();
        }
    }
    //獲取一個元素
    private E extract() {
        final Object[] items = this.items;
        E x = this.<E>cast(items[takeIndex]);
        items[takeIndex] = null;
        //改變take位置
        takeIndex = inc(takeIndex);
        --count;
        //通知lock的滿條件隊列上的線程
        notFull.signal();
        return x;
    }

可以看到put()和take()方法是阻塞隊列的真正實現方法,當隊列滿了時,put()方法被阻塞,直到有元素被取出,put()方法才能添加元素;當隊列爲空時,take()方法被阻塞直到有元素被添加,take()方法才能獲取元素

再來看看poll()方法

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //如果隊列爲空則返回null;否則返回一個元素
            return (count == 0) ? null : extract();
        } finally {
            lock.unlock();
        }
    }
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果隊列爲null且等待時間<=0則等待指定時間,否則返回null
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return extract();
        } finally {
            lock.unlock();
        }
    }

可以看到poll()方法也是非阻塞的.這個類用法、實現也都比較簡單.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章