并发容器

简介:

在并发场景下,同步容器的使用需要注意复合操作,伪同步,迭代等问题,同时效率也不高。为了解决这些问题,java提供了各种高性能的并发容器。下面我们一起来学习并发容器吧。

实现同步的另外一种思维方式–写时复制:

之前学习保证线程安全的思路是使用synchronized,显示锁和java sun包下Unsafe类提供的CompareAndSet()方法,简称CAS。通过synchronized和显示锁,以及循环CAS对同一资源的访问进行控制。而现在我们提到的写时复制,就是保证线程安全的另外一种思路。写时复制通过复制资源减少冲突。对于绝大部分访问都是读,且有大量并发线程要求读,只有个别线程进行写,而且只是偶尔写的场合,写时复制就是一个很好的解决方案。比如操作系统内部的进程管理和内存管理。在进程管理中,子进程经常共享父进程的资源,只有在写时才复制。在内存管理中,当多个程序同时访问同一个文件时,操作系统在内存中可能只会加载一次,只有程序写时才会复制,分配自己的内存。读写可以同步,写时通过显示锁RenntrantLock来控制只能一个线程写。

写时复制实现的List --CopyOnWriteArrayList:

特点:
(1)它是线程安全的,可以被多线程并发访问。
(2)它的迭代器不支持修改操作,但不会抛ConcurrentModificationException异常。
(3)支持一些复合操作。

支持的复合操作方法:

方法名返回值说明
addIfAbsent(E e)boolean不存在就添加,添加成功返回true,否则返回false
addAllIAbsent(Collection<? extends E> c)int批量添加元素,不存在才添加,返回实际添加的个数
 CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
        //下面三个方法都是通过synchronized关键字来保证原子性的,写时加锁都是加CopyOnWriteArrayList内部定义的锁,所以可以进行复合操作。
        copyOnWriteArrayList.add("1");
        copyOnWriteArrayList.addIfAbsent("2");
        copyOnWriteArrayList.addAllAbsent(Arrays.asList(new String[]{"3","4"}));
        copyOnWriteArrayList.indexOf("1");
CopyOnWriteArrayList实现机制:
/**
     * CopyOnWriteArrayList 内部是数组,每次更新都是以原子方式被整体更新。每次修改操作
     * 都会新建一个数组,复制原来的数组内容到新数组,在新数组上进行需要的修改,然后以原子的方式
     * 设置内部的数组引用,这就是写时复制。所有的读操作都是拿当前的数组引用访问数组,在读的过程中
     * 可能内部的数组引用已经被修改,但不会影响读操作,它依旧访问原数组内容。
     */
    static class CopyOnWriteList<E> {
        //transient 声明变量不被序列化
        //使用volatile,保证内存可见性,保证在写操作更新之后读操作可以看到
        private volatile transient Object[] array;
        //CopyOnWriteArrayList 读不需要锁,可以并行,读和写都可以并行,但多个线程不能同时写,
        //每个写操作都需要先获取锁
        private final ReentrantLock lock = new ReentrantLock();

        public CopyOnWriteList() {
            setArray(new Object[0]);
        }

        public final Object[] getArray(){
            return array;
        }

        public final void setArray(Object[] a){
            array = a;
        }

        /**
         *CopyOnWriteArrayList写时复制,加上显示锁ReentrantLock,写时只能一个线程写。
         *先把原来数组复制到新数组,在讲新添加的内容加到新数组,然后把内部数组的引用修改成新数组
         *
         */
        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 int indexOf(Object o){
            Object[] elements = getArray();
            return indexOfRange(o,elements,0,elements.length);
        }

        public static int indexOfRange(Object o,Object[] es,int from,int to){
            int i;
            if (o == null) {
                for(i = from; i < to; ++i) {
                    if (es[i] == null) {
                        return i;
                    }
                }
            } else {
                for(i = from; i < to; ++i) {
                    if (o.equals(es[i])) {
                        return i;
                    }
                }
            }

            return -1;
        }

    }

写时复制Set --Copy0nWriteArraySet:

CopyOnWriteArraySet实现了set接口,不包含重复元素。它的内部是通过CopyOnWriteArraySet实现的。

//CopyOnWriteArraySet内部是一个copyOnWriteArrayList。
private final CopyOnWriteArrayList<E> al;

    public CopyOnWriteArraySet() {
        this.al = new CopyOnWriteArrayList();
    }

    public CopyOnWriteArraySet(Collection<? extends E> c) {
        if (c.getClass() == CopyOnWriteArraySet.class) {
            CopyOnWriteArraySet<E> cc = (CopyOnWriteArraySet)c;
            this.al = new CopyOnWriteArrayList(cc.al);
        } else {
            this.al = new CopyOnWriteArrayList();
            this.al.addAllAbsent(c);
        }

    }
    //添加方法也都是用CopyOnWriteArrayList的add()方法
    public boolean add(E e) {
        return this.al.addIfAbsent(e);
    }

写时复制总结:

通过上面CopyOnWriteArrayList实现机制,我们可以看出写时复制性能比较低,不适用于元素个数比较多的集合。它们适合于读远多于写,集合不是很大的场合。写时复制是计算机程序中一种重要的思维和技术。

并发容器–ConcurrentHashMap:

ConcurrentHashMap特点:
(1)并发安全;
(2)直接支持一些原子复合操作;
(3)支持高并发,读操作完全并行,写操作支持一定程度的并行;
(4)相比同步容器迭代不用加锁,不会抛出异常。
(5)弱一致性:迭代时,修改的内容是迭代过的部分,就不会反映出来,未迭代的部分修改就会反映出来。

复合操作方法:

方法名返回值说明
putIfAbsent(K key,V value)V条件更新,有key就返回原来key的值,不更新值。没有key就设置key和value.返回null
remove(Object key,Object value)boolean条件删除,如果Map中有key,且对应的值为value,则删除,返回true,否则false
replace(K key,V oldValue,V newValue)boolean条件替换,如果Map中有key,且对应的值为oldValue,则替换为newValue,替换了返回true,否则false.。
replace(K key,V value)V条件替换,如果map中有key,则替换为value,返回原来的key对应的值,如果没有返回null
ConcurrentHashMap<String,Object> concurrentHashMap = new ConcurrentHashMap<>();
        concurrentHashMap.put("1",1);
        //条件更新,有key就返回原来key的值,不更新值。没有key就设置key和value.返回null
        Object str = concurrentHashMap.putIfAbsent("1",2);
        Object s = concurrentHashMap.putIfAbsent("key",3);
        System.out.println(concurrentHashMap.toString()+": " +s+ ": "+str);
        //条件删除,当map中存在key,且值为value,则删除返回true,否则false
        boolean flag = concurrentHashMap.remove("2",1);
        System.out.println(concurrentHashMap.toString() + ": " + flag);
        //条件替换,当map中存在key,且值为oldValue,则替换为newValue,返回true,否则返回false
        boolean reFlag = concurrentHashMap.replace("1",1,2);
        System.out.println(concurrentHashMap.toString() + ": " + reFlag);
        //条件替换,当map中有key就替换为value,返回true。否则返回false
        Object o = concurrentHashMap.replace("13",3);
        System.out.println(o);

ConcurrentHashMap高并发的基本机制:

(1)分段锁:同步容器使用的是synchronized和显示锁,所有线程都是竞争同一个锁。而ConcurrentHashMap采用分段锁技术,将数据分为多个段,而每个段都有一个独立的锁,每个段相当于一个独立的哈希表,分段的依据也是哈希值,无论是保存键值对还是根据键查找,都先根据键的哈希值映射到段,再在段对应的哈希表上进行操作。采用分段锁,可以大大提高并发度。多个段之间的可以并行读写。在对每个段进行读写的时候,ConcurrentHashMap也不是简单的使用锁进行同步,内部使用了CAS。对一些写采用原子方法的方法。对于写操作,需要获取锁,不能并行。但是读操作可以,多个读可以并行,写的时候也可以读,这使得ConcurrentHashMap的并行度远高于同步容器。ConcurrentHashMap迭代时,另外一个线程对容器作了修改,迭代还是可以进行,不会抛出异常。同时还保持弱一致性,就是修改的内容是迭代过的部分,就不会反映出来,未迭代的部分修改就会反映出来
(2)读不加锁。
(3)迭代时的弱一致性:

 final Map<String,Object> concurrentMap = new ConcurrentHashMap<>();
 //ConcurrentHashMap 迭代的弱一致性例子:
       concurrentMap.put("1",1);
       concurrentMap.put("2",2);
       Thread thread = new Thread(() -> {
           for (Map.Entry<String,Object> entry: concurrentMap.entrySet()) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(entry.getKey() + ": " +entry.getValue());
           }
       });

       thread.start();
       try {
           Thread.sleep(1000);
       }catch (InterruptedException i){
           i.printStackTrace();
       }
        concurrentMap.put("4",3);

并发队列:

并发队列分为两种,一种时无锁非阻塞式队列,一种是阻塞队列。无锁非阻塞指的是这些队列不加锁,所有的操作总是能立即执行,主要通过循环CAS实现并发安全。阻塞队列是都采用锁和条件,很多操作需要先获取锁或满足特定条件,获取不到锁或等待条件时,会阻塞,获取到锁或条件满足在返回。并发队列迭代都不会抛出ConcurrentModificationException异常,都是弱一致性。

无锁非阻塞式队列–ConcurrentLinkedQueue与ConcurrentLinkedDeque:

它们适合多个线程访问同一个队里的场合,都是基于链表实现的,都没有大小限制,无界的,与ConcurrentHashMap一样,它们的size不是一个常量运算,不能使用size作为条件。

普通阻塞队列–ArrayBlockingQueue与LinkedBlockingQueue以及LinkedBlockingDeque:

(1)ArrayBlockingQueue实现了BlockingQueue接口,它是基于循环数组实现的,有界,创建时需要指定大小,且在运行过程中不会改变。ArrayBlockingQueue内部有一个数组存储元素,有两个索引表示头和尾,有一个变量表示当前元素个数,有一个锁保护所有访问,有’不满’和’不空’两个条件用于协作。

//实现了BlockingQueue接口,入队和出队时可能等待
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
    private static final long serialVersionUID = -817911632652898426L;
    final Object[] items;
    int takeIndex;
    int putIndex;
    int count;
    final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;
    transient ArrayBlockingQueue<E>.Itrs itrs;
 
  //入队,如果队列满,等待直到队列有空间,传入值为空会抛出空指针异常   
  public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        ReentrantLock lock = this.lock;
        lock.lockInterruptibly();

        try {
            while(this.count == this.items.length) {
                this.notFull.await();
            }

            this.enqueue(e);
        } finally {
            lock.unlock();
        }

    }
 //出队,如果队列为空,等待直到队列不为空,返回头部元素 
public E take() throws InterruptedException {
        ReentrantLock lock = this.lock;
        lock.lockInterruptibly();

        Object var2;
        try {
            while(this.count == 0) {
                this.notEmpty.await();
            }

            var2 = this.dequeue();
        } finally {
            lock.unlock();
        }

        return var2;
    }
  
   //入队,如果队列满,最多等待指定的时间,如果超时还是满,返回false,传入值为空会抛出空指针异常
    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        Objects.requireNonNull(e);
        long nanos = unit.toNanos(timeout);
        ReentrantLock lock = this.lock;
        lock.lockInterruptibly();

        try {
            boolean var8;
            while(this.count == this.items.length) {
                if (nanos <= 0L) {
                    var8 = false;
                    return var8;
                }

                nanos = this.notFull.awaitNanos(nanos);
            }

            this.enqueue(e);
            var8 = true;
            return var8;
        } finally {
            lock.unlock();
        }
    }
    
    //出队,如果队列为空,最多等待指定的时间,如果超时还是空,返回null
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        ReentrantLock lock = this.lock;
        lock.lockInterruptibly();

        try {
            Object var7;
            while(this.count == 0) {
                if (nanos <= 0L) {
                    var7 = null;
                    return var7;
                }

                nanos = this.notEmpty.awaitNanos(nanos);
            }

            var7 = this.dequeue();
            return var7;
        } finally {
            lock.unlock();
        }
    }
    public E peek() {
        ReentrantLock lock = this.lock;
        lock.lock();

        Object var2;
        try {
            var2 = this.itemAt(this.takeIndex);
        } finally {
            lock.unlock();
        }

        return var2;
    }

(2)Link edBlockingQueue也实现了BlockingQueue接口,它是基于单向链表实现的,创建时可以指定最大长度,也可以不指定,默认是无限的,节点时动态创建的。LinkedBlockingQueue实现机制也是使用两个锁和两个条件,一个锁保护头部,一个锁保护尾部,每个锁关联一个条件。

public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
    private static final long serialVersionUID = -6903933977591709194L;
    private final int capacity;
    private final AtomicInteger count;
    transient LinkedBlockingQueue.Node<E> head;
    private transient LinkedBlockingQueue.Node<E> last;
    private final ReentrantLock takeLock;
    private final Condition notEmpty;
    private final ReentrantLock putLock;
    private final Condition notFull;
public void put(E e) throws InterruptedException {
        if (e == null) {
            throw new NullPointerException();
        } else {
            LinkedBlockingQueue.Node<E> node = new LinkedBlockingQueue.Node(e);
            ReentrantLock putLock = this.putLock;
            AtomicInteger count = this.count;
            putLock.lockInterruptibly();

            int c;
            try {
                while(count.get() == this.capacity) {
                    this.notFull.await();
                }

                this.enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < this.capacity) {
                    this.notFull.signal();
                }
            } finally {
                putLock.unlock();
            }

            if (c == 0) {
                this.signalNotEmpty();
            }

        }
    }
 public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        if (e == null) {
            throw new NullPointerException();
        } else {
            long nanos = unit.toNanos(timeout);
            ReentrantLock putLock = this.putLock;
            AtomicInteger count = this.count;
            putLock.lockInterruptibly();

            int c;
            try {
                while(count.get() == this.capacity) {
                    if (nanos <= 0L) {
                        boolean var10 = false;
                        return var10;
                    }

                    nanos = this.notFull.awaitNanos(nanos);
                }

                this.enqueue(new LinkedBlockingQueue.Node(e));
                c = count.getAndIncrement();
                if (c + 1 < this.capacity) {
                    this.notFull.signal();
                }
            } finally {
                putLock.unlock();
            }

            if (c == 0) {
                this.signalNotEmpty();
            }

            return true;
        }
    }   
public E take() throws InterruptedException {
        AtomicInteger count = this.count;
        ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();

        Object x;
        int c;
        try {
            while(count.get() == 0) {
                this.notEmpty.await();
            }

            x = this.dequeue();
            c = count.getAndDecrement();
            if (c > 1) {
                this.notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }

        if (c == this.capacity) {
            this.signalNotFull();
        }

        return x;
    }
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        AtomicInteger count = this.count;
        ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();

        Object x;
        int c;
        try {
            while(true) {
                if (count.get() != 0) {
                    x = this.dequeue();
                    c = count.getAndDecrement();
                    if (c > 1) {
                        this.notEmpty.signal();
                    }
                    break;
                }

                if (nanos <= 0L) {
                    Object var10 = null;
                    return var10;
                }

                nanos = this.notEmpty.awaitNanos(nanos);
            }
        } finally {
            takeLock.unlock();
        }

        if (c == this.capacity) {
            this.signalNotFull();
        }

        return x;
    }

(3)LinkedBlockingDeque也实现了BlockingQueue接口,它是基于双向链表实现的,创建时可以指定最大长度,默认是无限。LinkedBlockingDeque也是使用一个锁和两个条件,使用锁和不空和不满两个条件来控制并发。

优先级阻塞队列–priorityQueue :

PriorityQueue是优先级队列,按优先级出队的。它是无界的,内部的数组大小会动态扩展。要求元素要么实现comparable接口或者创建时提供一个comparator对象。

public class PriorityQueue<E> extends AbstractQueue<E> implements Serializable {
    private static final long serialVersionUID = -7720805057305804111L;
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    transient Object[] queue;
    int size;
    private final Comparator<? super E> comparator;
    transient int modCount;
延时阻塞队列–DelayQueue:

它是无界的,要求每个元素都是实现Delayed接口.它扩展了comparable接口,delayed每个元素都是可比较的。它的getDelay返回一个给定时间单位的unit整数,表示在延迟 多长时间。如果小于等于0,则表示不在延迟。DelayedQueue可用于定时任务,它按元素的延时时间出队。它的特殊之处在于,只有元素的延时过期之后才能被从队列中拿走,也就是说,take方法总是返回第一个过期的元素。如果没有,则阻塞等待。

public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
    private final transient ReentrantLock lock = new ReentrantLock();
    private final PriorityQueue<E> q = new PriorityQueue();
    private Thread leader;
    private final Condition available;
LinkedTransferQueue:

它是基于链表实现的,无界的,它实现了TransferQueue,TransferQueue是BlockingQueue的子接口。但增加了一些额外的功能。生产者在往队列中放元素时,可以等待消费者接受后再返回。适用于一些信息传递类型的应用。
提供了一些方法:

方法名返回值说明
tryTransfer(E e)boolean如果有消费者在等待(执行take或限时的poll),直接转给消费者,返回true,否则false,不入队。
Transfer(E e)boolean如果有消费者在等待,直接转给消费者,否则入队,阻塞等待直到被消费者接收,响应中断。
tryTransfer(E e,long timeout,TimeUnit unit)boolean如果有消费者等待,直接转给消费者,返回true,
* 否则入队,阻塞 等待限时时间,如果最后被消费者接收,返回true.响应中断
hasWaitingConsumerboolean是否有消费者在等待
getWaitingConsumerint等待的消费者个数。

总结:

本章节列举出了常用的并发容器和并发队列,以及实现并发的另外一种思路,写时复制。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值