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()方法也是非阻塞的.这个类用法、实现也都比较简单.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值