IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Java并发工具学习(七)——说说CopyOnWriteArrayList和BlockingQueue -> 正文阅读

[系统运维]Java并发工具学习(七)——说说CopyOnWriteArrayList和BlockingQueue

前言

上一篇博客简单介绍了ConcurrentHashMap,这个也是并发容器之一,这一篇博客继续介绍另外两个并发容器,CopyOnWriteArrayList和BlockingQueue,同样不会深入到具体源码级别,只简单说说源码中认为比较常见的点即可。

CopyOnWriteArrayList

在上一篇博客中我们简单提到过,为了替代SynchronizedMap,才有了ConcurrentHashMap,原因是SynchronizedMap锁的粒度太大。ConcurrentHashMap锁的粒度稍微小一点,并发效率更高。同样的情况,Vector和SynchronizedList也存在同样的问题,为了解决Vector和SynchronizedList锁粒度太大和迭代时无法编辑的问题,于是有了CopyOnWriteArrayList。同类型的还有CopyOnWriteArraySet。

在CopyOnWriteArrayList中,读取是完全不用加锁的,写入也不会阻塞读取操作,只有写入与写入之间需要同步互斥等待

相关实例

其核心原理其实比较简单,CopyOnWrite本质上就是常说的读写分离,CopyOnWriteArrayList是在修改时对原有的List数据重新复制了一份,并开辟了新的内存空间,在新的数据上进行修改,修改完成之后,再将原有的数据引用指向当前新的内存地址。在修改数据期间读数据依旧读取的是原有的内存地址。

/**
 * autor:liman
 * createtime:2021/11/21
 * comment:CopyOnWriteArrayList 使用实例
 */
@Slf4j
public class CopyOnWriteArrayListUseDemo {

    public static void main(String[] args) {
        ordinalListDemo();
        copyOnWriteArrayListDemo();
    }

    public static void ordinalListDemo(){
        ArrayList<String> oridinalList = new ArrayList<>();
        oridinalList.add("1");
        oridinalList.add("2");
        oridinalList.add("3");
        oridinalList.add("4");
        oridinalList.add("5");
        Iterator<String> iterator = oridinalList.iterator();
        while (iterator.hasNext()) {
            System.out.println("oridinalList is " + oridinalList);
            String next = iterator.next();//删除"5"之后,下次迭代会抛异常
            System.out.println("current node is " + next);
            if (next.equals("2")) {
                //普通ArrayList在遍历期间对集合进行修改,是会抛异常的
                oridinalList.remove("5");//这里在遍历的时候对集合进行修改,这会抛异常的
            }
        }
    }

    /**
     * CopyOnWriteArrayList的实例
     */
    public static void copyOnWriteArrayListDemo(){
        CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
        copyOnWriteArrayList.add("1");
        copyOnWriteArrayList.add("2");
        copyOnWriteArrayList.add("3");
        copyOnWriteArrayList.add("4");
        copyOnWriteArrayList.add("5");
        Iterator<String> iterator = copyOnWriteArrayList.iterator();
        while (iterator.hasNext()) {
            System.out.println("oridinalList is " + copyOnWriteArrayList);
            String next = iterator.next();
            System.out.println("current node is " + next);
            if (next.equals("2")) {
                //CopyOnWriteArrayList在遍历的时候,进行修改是不会抛错的,依旧能正常修改
                copyOnWriteArrayList.remove("5");
            }

            if(next.equals("3")){
                copyOnWriteArrayList.add("new node");
            }
        }
    }
}

上述代码中的第二个方法运行实例如下所示

oridinalList is [1, 2, 3, 4, 5]
current node is 1
oridinalList is [1, 2, 3, 4, 5]
current node is 2
oridinalList is [1, 2, 3, 4]
current node is 3
oridinalList is [1, 2, 3, 4, new node]
current node is 4
oridinalList is [1, 2, 3, 4, new node]
current node is 5

注意最后一行输出,在编辑完成CopyOnWriteArrayList之后,输出的当前节点数据依旧是5,也就是说,修改的数据,并没有即时生效。似乎数据虽然修改了,但是读取依旧读取的是原来的数据。

第二个代码实例

/**
 * autor:liman
 * createtime:2021/11/21
 * comment:多个迭代器的数据决定因素
 * 迭代器所能遍历的数据,取决于迭代器初始化的时刻
 */
@Slf4j
public class CopyOnWriteArrayListMultiIterDemo {

    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> nums = new CopyOnWriteArrayList<Integer>(new Integer[]{1,2,3});
        Iterator<Integer> iteratorOne = nums.iterator();
        System.out.println(nums);//1,2,3
        nums.add(5);
        System.out.println(nums);//1,2,3,5
        Iterator<Integer> iteratorTwo = nums.iterator();
        iteratorOne.forEachRemaining(System.out::print);//1,2,3
        System.out.println();
        iteratorTwo.forEachRemaining(System.out::print);//1,2,3,5
    }
}

相关源码

其中的add方法

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    //采用的是ReentrantLock
    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();
    }
}

其中的get方法

public E get(int index) {
    return get(getArray(), index);
}
//简单的直接返回,并没有加锁的逻辑
private E get(Object[] a, int index) {
    return (E) a[index];
}

get直接返回并没有任何加锁的逻辑

BlockingQueue

BlockingQueue是阻塞队列,这个相比于普通队列,在读取数据和存入数据的时候,存在可能阻塞的差异。关于Java中常见有以下几种队列。

阻塞队列的几个方法

这张图只是列出了几种常用的队列。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XyYdxbAP-1638009136590)(F:\blog_doc\blogPic\sync&JUC-learn\image-20211124200834327.png)]

ConcurrentLinkedQueue是一个非阻塞队列,同样也能保证线程安全。

所谓阻塞队列,其实就是带有阻塞功能的队列,其是线程安全的,通常用于生产者从一端存放数据,消费者从另一端读取数据(貌似队列大部分场景都是这个用法),而所谓的阻塞队列是指,如果队列为空,则消费者从队列读取数据会被阻塞;如果队列数据已满,生产者往队列存放数据会被阻塞。

通常的阻塞队列中会有如下一些方法

put,take这两个会阻塞,
队列满的时候put会阻塞,队列空的时候take会阻塞
add,remove,elementadd,如果队列满了,直接抛异常
remove,如果队列空了,直接抛异常
element,返回队列头元素,如果空抛异常
offer,poll,peekoffer,添加,队列满了返回false
poll,取出头元素,会在队列中删除头元素,队列空返回null
peek,返回头元素,但不删除取出的元素

典型的阻塞队列

常用的阻塞队列有ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,PriorityBlockingQueue。这里只简单介绍一下前两种

ArrayBlockingQueue

ArrayBlockingQueue是一个有界队列,在初始化的时候需要指定容量,同时还可以指定是否是公平的,如果指定其为公平的,则会将队列中等待了最长时间的数据优先处理。

/**
 * autor:liman
 * createtime:2021/11/24
 * comment:有界,需要指定容量,可以指定公平还是不公平
 * 实例:10个面试者,一个面试官,一个休息室有三个位置
 */
@Slf4j
public class ArrayBlockingQueueDemo {

    public static void main(String[] args) {
        //这里的ArrayBlockingQueue 类似于3把椅子
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        Interviewer interviewer = new Interviewer(arrayBlockingQueue);
        Consumer consumer = new Consumer(arrayBlockingQueue);
        new Thread(interviewer).start();
        new Thread(consumer).start();
    }
}

//模拟的是面试者
class Interviewer implements Runnable {
    private BlockingQueue 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+1);
            try {
                //进入队列,等待面试
                queue.put(candidate);
                System.out.println("候选人" + (i + 1) + "等待面试");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            //结束的标志位
            queue.put("allin");
        } 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("allin"))) {
                System.out.println(msg + "正在面试");
            }
            System.out.println("所有候选人都面试完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

其put源码如下

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();
    }
}

LinkedBlockingQueue

这是一个无界的阻塞队列,并不是真正意义上的无界,而是容量为Integer.MAX_VALUE,意味着很大程度生产者并不会出现阻塞,底层数据结构是链表。同时其维护了两把锁,一个putLock,一个takeLock。

其中的put源码

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
	//获取put锁
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
		 //队列已满,等待
        while (count.get() == capacity) {
            notFull.await();
        }
		//入队,修改队列长度
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

其中的take源码

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    //获取takeLock
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        //如果为空,则等待
        while (count.get() == 0) {
            notEmpty.await();
        }
        //出队列,并修改队列长度
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

其他阻塞队列

PriorityBlockingQueue可以简单理解为PriorityQueue的线程安全版本,支持优先级,在容量不够的时候,会进行扩容,通知支持按照元素的compareTo的结果进行排序。

SynchronousQueue没有peek等函数,因为peek是取出头结点,但是SynchronousQueue的容量是0,因此没有peek方法,newCachedThreadPool线程池就是使用的这个队列作为任务队列。

总结

简单梳理了一下CopyOnWriteArrayList和BlockingQueue的内容,其中CopyOnWriteArrayList适用于读多写少的场景,BlockingQueue其实除了用于生产者消费者模式之外,还大部分用于线程池中的任务队列。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-11-28 11:39:26  更:2021-11-28 11:40:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 1:56:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码