java并发容器介绍

1、概览

  • ConcurrentHashMap:线程安全的HashMap
  • CopyOnWriteArrayList:线程安全的List
  • BlockingQueue:接口,表示阻塞队列,非常适合用于作为数据共享的通道。
  • ConcurrentLinkedQueue :高效的非阻塞并发队列,使用链表实现。可以看作是一个线程安全的LinkedList。
  • ConcurrentSkipListMap:是一个Map,使用跳表的数据结果进行快速查找。

2、ConcurrentHashMap

  • 先介绍一下hashMap
  • 在这里插入图片描述

3、JDK7中的 concurrenthashmap

  • 在这里插入图片描述

  • Java7中的ConcurrenHashMap最外层是多个segment,每个segment的底层数据结构于HashMap类似,仍然是数组和链表组成的拉链法

  • 每个segment独立上ReentranLock锁,每个segment之间互不影响,提高了并发效率。

  • ConcurrentHashMap默认有16个segments,所以最多可以同时支持16个线程并发写(操作在不同的segment上)。这个默认值可以在初始化的时候设置为其它的值,但是一旦初始化以后,是不可以进行扩容的。

4、JDK8中的 concurrenthashmap

  • 几乎是把jdk7中的ConcurrentHashMap重写了,代码量从一千多行变为现在的六千多行。

    在实现和结构上有很大的区别

    • 不在采用segment,而是采用node。

    • 数据结构、Hash碰撞、保证并发安全、查询复杂度等方面不同。

    • 保证线程安全的方式变为了synchronized和CAS

    • 结构 (借鉴hashmap1.8的设计结构,链表加红黑树)在这里插入图片描述

    • 保证所有的put和get操作都是线程安全的。但是不能保证组合操作是线程安全的。

3、CopyOnWriteArrayList(适合读多写少的场景)

3.1诞生的原因

  • 代替Vecotr和SynchronizedList,就和ConcurrentHashMap代替SynchronizedMap的原因是一样的。Vecotr和SynchronizedList的锁的粒度太大,效率低。

3.2 适用场景

  • 读操作尽可能的快,写操作慢一点也没有问题。例如:黑名单,每日更新……
  • 读写锁规则的升级:读取是不用加锁的,并且更厉害的是**,写入也不会阻塞读取操作**,只有写入和写入之间需要进行同步操作。

3.3代码演示

  • 演示CopyOnWriteArrayList可以在迭代的过程中修改数组内容,但是ArrayList不行,对比


public class CopyOnWriteArrayListDemo1 {

    public static void main(String[] args) {
         //如果是ArrayList,到这里的时候5被删除了,就会报错
//        ArrayList<String> list = new ArrayList<>();
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");

        Iterator<String> iterator = list.iterator();

        while (iterator.hasNext()) {
            System.out.println("list is" + list);
            String next = iterator.next();
            System.out.println(next);

            if (next.equals("2")) {
                list.remove("5");
            }
            if (next.equals("3")) {
                list.add("3 found");
            }
        }
    }
}

在这里插入图片描述

尽管已经修改list的内容,但是迭代器中的内容任然没有发生改变。–》修改的时候将内存中的值复制一份,对复制的数据进行操作。

3.4 实现原理(copyOnWrite)

  • CopyOnWrite的含义
    • 修改的时候将内存中的值复制一份,对复制的数据进行操作。修改完成后将内存指向这个地址。
    • 这样就可以实现在读的同时实现修改的操作。提高读效率,由于写的次数很少,就不用担心读取不到最新的数据。
    • **创建新副本,读写分离。**最后再替换回去。
    • “**不可变”**原理,旧的容器不会发生改变。

3.5 缺点

  • 数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据能马上得到,就不要使用CopyOnWrite.
  • 内存占用问题:因为CopyOnWrite的写是复制机制,所以再进行写操作的时候,内存里会同时驻扎两个对象。

3.6源码分析

在这里插入图片描述

在这里插入图片描述

  • get方法没有加锁,可以随意读取。但是写操作是加锁同步的

在这里插入图片描述

在这里插入图片描述

4、并发队列Queue(阻塞队列–BlockingQueue)

4.1为什么要采用队列

  • 用队列可以在线程中传递数据:生产者消费者模式,银行转账。
  • 考虑线程安全的重任转移到了**“队列”**上。

4.2常见队列

  • Queue
  • BlockingQueue:阻塞队列。如果队列为空,则读取的时候一直等待。如果队列满了,插入会一直等待。
  • 在这里插入图片描述

4.3什么是阻塞队列

  • 阻塞队列的一端给生产者放数据用,另一端给消费者拿数据。阻塞队列是线程安全的,所以生产者和消费者都可以是多线程的。
  • 两个具有特色的阻塞功能:
    • take()方法:湖区并移除队列的头结点,一旦队列中无数据,则阻塞,直到队列中有数据。
    • put()方法:插入元素。但是如果队列已满,则阻塞,直到队列里有了空闲空间。
  • **是否有界:**这时一个非常重要的属性,无界意味着里面可以容纳非常多的数据。
  • 阻塞队列和线程池的关系:阻塞队列是线程池的重要组成部分。

4.4BlockingQueue主要方法。

  • put,take //这些方法失败的时候会阻塞
  • add,remove,element //这些方法不满足的时候抛出异常
  • offer:添加一个元素,返回布尔值。 poll:取出一个值,空的话就返回空值,取出并删除。peek:取出一个值,但是不会进行删除。

4.5ArrayBlockingQueue

  • 有界

  • 指定容量

  • 公平:还可以指定是否需要保证公平(等待了最长时间的线程被优先处理),但是会造成性能损耗。

  • 代码演示:

    
    /**
     * 描述:     TODO 模拟候选人进行面试
     */
    public class ArrayBlockingQueueDemo {
    
    
        public static void main(String[] args) {
    
            ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);
    
            Interviewer r1 = new Interviewer(queue);
            Consumer r2 = new Consumer(queue);
            new Thread(r1).start();
            new Thread(r2).start();
        }
    }
    
    class Interviewer implements Runnable {
    
        BlockingQueue<String> queue;
    
        public Interviewer(BlockingQueue queue) {
            this.queue = queue;
        }
    
        @Override
        public void run() {
            System.out.println("10个候选人都来啦");
            for (int i = 0; i < 10; i++) {
                String candidate = "Candidate" + i;
                try {
                    queue.put(candidate);
                    System.out.println("安排好了" + candidate);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                queue.put("stop");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class Consumer implements Runnable {
    
        BlockingQueue<String> queue;
    
        public Consumer(BlockingQueue queue) {
    
            this.queue = queue;
        }
    
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String msg;
            try {
                while(!(msg = queue.take()).equals("stop")){
                    System.out.println(msg + "到了");
                }
                System.out.println("所有候选人都结束了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

4.6 LinkedBlockingQueue

  • 无界

  • 容量值为Integer.MAX_VALUE

  • 内部结构:Node,两个锁。分析put方法。

  • 源码分析:

    • 结点结构在这里插入图片描述

    • put方法分析

    在这里插入图片描述

4.7PriorityBlockingQueue.

  • 支持优先级
  • 自然顺序(而不是先进先出)
  • 无界队列(可以进行扩容)

4.8SynchonousQueue

  • 容量为0
  • 不需要持有元素,直接进行接收传递
  • 效率高,是一个极好的用来直接传递的并发数据结构。
  • 是线程池Executors.newCachedThreadPool()的使用的阻塞队列。

5、非阻塞并发队列

  • 并法宝中的非阻塞队列中只有ConcurrentLinkedQueued这一种,使用链表作为数据结构,使用CAS非阻塞算法实现线程安全,适合用在对性能要求较高的并发场景,用的情况较少。

猜你喜欢

转载自blog.csdn.net/qq_45372719/article/details/108673749
今日推荐