并发容器

ConcurrentHashMap

线程安全且高效,并发中使用HashMap可能导致程序死循环,而线程安全的HashTable效率又非常地下,

线程不安全的HashMap

多线程下,HashMap进行put操作会引起死循环,导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,最终产生死循环获取Entry,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

HashTable容器使用synchronized关键字保证线程安全,竞争激烈情况下效率低下,一个线程访问同步方法时,其他线程访问同步方法时会进入阻塞或轮询。

ConcurrentHashMap使用锁分段技术,将数据分段,每段一把锁,不同线程访问不同的数据段,一个线程占用一个数据段的锁时,不影响其他段被其他线程访问。

ConcurrentLinkedQueue

线程安全的队列,安全的实现的方式:1.通过一个锁(入队和出队同一把锁)或两个锁实现,阻塞。2.使用循环CAS的方式来实现,非阻塞。

ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,采用FIFO的规则进行排序,使用volidate和CAS来实现。

 

阻塞队列

一个支持两个附加操作的队列,支持阻塞的插入和移除方法。当前队列满时,队列会阻塞插入元素的线程,直到队列不满。而移除时,当队列为空时,获取元素的线程会阻塞等待队列变为非空。

  • 抛出异常----当队列满时,在插入元素抛出异常,为空时,再获取元素,抛出异常
  • 返回特殊值----当往队列插入元素时,会返回元素是否插入成功,成功返回true,移除的话,没有返回null。
  • 一直阻塞----队列满了,再插入,会阻塞直到队列不满或响应中断,队列为空时,获取元素会阻塞直到队列不为空。
  • 超时退出----当阻塞队列满时,再插入,阻塞一定时间后退出。

无界队列不会出现满的情况,所以put或offer永远不会阻塞。

提供了7个阻塞对列

ArrayBlockingQueue:数组构成的有界阻塞队列,队列按照FIFO原则进行排序。

LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,FIFO,默认最大长度Integer.MAX_VALUE。

PriorityBlockingQueue:一个优先级排序的无界阻塞队列,自然序升序,可自定义实现compareTo方法指定元素排序。

DelayQueue:一个使用优先级队列实现的无界阻塞队列。

SynchronousdQueue:一个不存储元素的阻塞队列。

LinkedTransferQueue:链表构成的无界阻塞队列。

LinkedBlockingDeque:一个由链表构成的双向阻塞队列。

ArrayBlockingQueue

使用ReentrantLock和 Condition,等待/通知模式。

/** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;

public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {  //如果满了阻塞
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try { //为空阻塞
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
 public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }
    /**
     * Inserts element at current put position, advances, and signals.
     * Call only when holding lock.
     */
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

    /**
     * Extracts element at current take position, advances, and signals.
     * Call only when holding lock.
     */
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

LinkedBlockingQueue

使用了两个lock,插入和获取分别一个,以及两个Condition


    /** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();

PriorityBlockingQueue


    private final ReentrantLock lock;

    private final Condition notEmpty;

public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        int n, cap;
        Object[] array;
        while ((n = size) >= (cap = (array = queue).length))
            tryGrow(array, cap);
        try {
            Comparator<? super E> cmp = comparator;
            if (cmp == null)
                siftUpComparable(n, e, array);
            else
                siftUpUsingComparator(n, e, array, cmp);
            size = n + 1;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
        return true;
    }
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        E result;
        try {
            while ( (result = dequeue()) == null)
                notEmpty.await();
        } finally {
            lock.unlock();
        }
        return result;
    }

DelayQueue

队列使用PriorityQueue,队列中的元素必须实现Delayed接口,可指定多久才能从队列中获取当前元素,延迟期满后,才能从队列中提取元素。缓存系统设计、定时任务设计。

    private final transient ReentrantLock lock = new ReentrantLock();
    private final PriorityQueue<E> q = new PriorityQueue<E>();

leader是一个等待获取队列头部元素的线程。如果leader不等于空,
表示已经有线程在等待获取队列的头元素。
所以,使用await()方法让当前线程等待信号。
如果leader等于空,则把当前线程设置成leader,
并使用awaitNanos()方法让当前线程等待接收信号或等待delay时间

    private Thread leader = null;

    private final Condition available = lock.newCondition();


    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                E first = q.peek();
                if (first == null)
                    available.await();
                else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        return q.poll();
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }
SynchronousQueue
SynchronousQueue 是一个不存 元素的阻塞 列。每一个 put 操作必 等待一个 take 操作,否则阻塞 不能 继续添加元素。
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }
SynchronousQueue 可以看成是一个 球手, 负责 把生 线 理的数据直接 传递给 者线 程。 列本身并不存 任何元素,非常适合 传递 景。 SynchronousQueue的吞吐量高于LinkedBlockingQueue ArrayBlockingQueue
 
LinkedTransferQueue
于其他阻 塞队 列, LinkedTransferQueue 多了 tryTransfer transfer 方法。

 

  • 如果当前有消者正在等待接收元素(消者使用take()方法或带时间限制的poll()方法 时),transfer方法可以把生入的元素立刻transfer传输者。如果没有消者在等 待接收元素,transfer方法会将元素存放在列的tail点,并等到元素被消者消了才返回
  • tryTransfer 方法是用来 探生 入的元素是否能直接 传给 者。如果没有消 者等待接收元素,则 返回 false 。和 transfer 方法的区 tryTransfer 方法无 者是否接收,方法 立即返回,而transfer 方法是必 等到消 者消 了才返回。
LinkedBlockingDeque
成的双向阻塞 队列,可从两端插入或移除元素。在初始化LinkedBlockingDeque时可以设置容量防止其过度膨胀。另外,双向阻塞队列可以运用在“工作窃取”模式中。
 
阻塞队列的实现原理

使用通知模式,当生产者向满的队列中添加元素时会阻塞生产者,当消费者消费队列中的一个元素时,会通知当前队列可用。使用Condition来实现,阻塞生产者主要通过LookSupport.park(this)。只有4种方式才能从该方法返回。
  • 与park对应的unpark执行或已经执行(先执行unpark,再执行park)时,
  • 线程被中断
  • 等待time后
  • 异常现象发生
Fork/Join
把一个大任务分割为若干个小任务,最终汇总每个小任务结果得到大任务结果的框架。Fork将大任务切分为若干个子任务并行,Join则是合并这些子任务的执行结果,得到大任务的结果。
工作窃取:某个线程从其他队列里窃取任务来执行。把任务划分为多个互不相干的子任务,每个子任务放到不同的队列中,并为每个队列分配一个线程来执行队列中的任务,线程与队列一一对应,如果一个队列里的任务执行完了,但是另一个队列还为完成,这是完成的线程到未完成的队列中窃取一个任务来执行。这是会两个线程同时访问队列,为了避免竞争,使用双端队列,被窃取的任务从双端队列的头部拿去任务,窃取任务的线程从尾部拿去任务。 充分利用线程进行并行计算,减少线程之间的竞争。但是当队列里只剩下一个任务是,还是存中竞争,而且窃取算法会消耗更多的系统资源,多个线程和多个双端队列。
必须继承 RecursiveTask
static class CountTask extends RecursiveTask<Integer> {
        //是否分割任务的阈值
        private static final int YUE = 2;
        private int start;
        private int end;

        public CountTask(int start, int end) {
            this.end = end;
            this.start = start;
        }

        @Override
        protected Integer compute() {
            int sum = 0;
            boolean canCompute = (end - start) <= YUE;
            if (canCompute) {
                for (int i = start; i < end; i++) {
                    sum += i;
                }
            } else {
                int middle = (start + end) / 2;
                CountTask left = new CountTask(start, middle);
                CountTask right = new CountTask(middle + 1, end);
                //执行子任务
                left.fork();
                right.fork();
                //合并结果
                sum = left.join() + right.join();
            }
            return sum;
        }
    }
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ForkJoinPool pool = new ForkJoinPool();
        CountTask task = new CountTask(1, 4);
        ForkJoinTask<Integer> result = pool.submit(task);
        result.get();
        //执行过程中主线程是无法捕获异常的。
        //通过判断来得知
        task.isCompletedAbnormally();//是否已经抛出异常或已经取消。
    }

原理:

    public final ForkJoinTask<V> fork() {
        Thread t;
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
             //异步执行这个任务  
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
            ForkJoinPool.common.externalPush(this);
        return this;
    }
final void push(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a; ForkJoinPool p;
            int b = base, s = top, n;
            if ((a = array) != null) {    // ignore if queue removed
                int m = a.length - 1;     // fenced write for task visibility
                U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
                U.putOrderedInt(this, QTOP, s + 1);
                if ((n = s - b) <= 1) {
                    if ((p = pool) != null)
                       //当前任务存放在ForkJoinTask数组队列里。
                        //然后再调用ForkJoinPool的 
                     //signalWork()方法唤醒或创建一个工作线程来执行任务
                        p.signalWork(p.workQueues, this);
                }
                else if (n >= m)
                    growArray();
            }
        }
    public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);
        return getRawResult();
    }

    private int doJoin() {
        int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
//通过任务状态来判断返回什么结果。
任务状态有4种:已完成(NORMAL)、被取消(CANCELLED)、信号(SIGNAL)和出现异常 (EXCEPTIONAL)

如果任务状态是已完成,则直接返回任务结果。 
如果任务状态是被取消,则直接抛出CancellationException。 
如果任务状态是抛出异常,则直接抛出对应的异常。

        return (s = status) < 0 ? s :
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
            (w = (wt = (ForkJoinWorkerThread)t).workQueue).
            tryUnpush(this) && (s = doExec()) < 0 ? s :
          //阻塞当前线程等待获取结果
            wt.pool.awaitJoin(w, this, 0L) :
            externalAwaitDone();
    }

    final int doExec() {
        int s; boolean completed;
        if ((s = status) >= 0) {
            try {
                completed = exec();
            } catch (Throwable rex) {
                return setExceptionalCompletion(rex);
            }
            if (completed)
                s = setCompletion(NORMAL);
        }
        return s;
    }

首先通过查看任务的状态,看任务是否已经执行完成,如果执行完成, 则直接返回任务状态;
如果没有执行完,则从任务数组里取出任务并执行。
如果任务顺利执行完成,则设置任务状态为NORMAL,
如果出现异常,则记录异常,并将任务状态设置为EXCEPTIONAL

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值