高并发编程:并发容器

news2024/11/18 23:44:48

一、概述

常见的容器如下图,我们会挑选高并发中常用的容器进行介绍。

二、ConcurrentHashMap

个ConcurrentHashMap提高效率主要提高在读上面,由于它往里插的时候内部又做了各种各样的判断,本来是链表的,到8之后又变成了红黑树,然后里面又做了各种各样的cas的判断,所以他往里插的数据是要更低一些的。HashMap和Hashtable虽然说读的效率会稍微低一些,但是它往里插的时候检查的东西特别的少,就加个锁然后往里一插。所以,关于效率,还是看你实际当中的需求。用几个简单的小程序来给大家列举了这几个不同的区别。

public class T04_TestConcurrentHashMap {

    static Map<UUID, UUID> m = new ConcurrentHashMap<>();

    static int count = Constants.COUNT;
    static UUID[] keys = new UUID[count];
    static UUID[] values = new UUID[count];
    static final int THREAD_COUNT = Constants.THREAD_COUNT;

    static {
        for (int i = 0; i < count; i++) {
            keys[i] = UUID.randomUUID();
            values[i] = UUID.randomUUID();
        }
    }

    static class MyThread extends Thread {
        int start;
        int gap = count/THREAD_COUNT;

        public MyThread(int start) {
            this.start = start;
        }

        @Override
        public void run() {
            for(int i=start; i<start+gap; i++) {
                m.put(keys[i], values[i]);
            }
        }
    }

    public static void main(String[] args) {

        long start = System.currentTimeMillis();

        Thread[] threads = new Thread[THREAD_COUNT];

        for(int i=0; i<threads.length; i++) {
            threads[i] =
            new MyThread(i * (count/THREAD_COUNT));
        }

        for(Thread t : threads) {
            t.start();
        }

        for(Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        long end = System.currentTimeMillis();
        System.out.println(end - start);

        System.out.println(m.size());

        //-----------------------------------

        start = System.currentTimeMillis();
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(()->{
                for (int j = 0; j < 10000000; j++) {
                    m.get(keys[10]);
                }
            });
        }

        for(Thread t : threads) {
            t.start();
        }

        for(Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

三、BlockingQueue

BlockingQueue,是我们后面讲线程池需要用到的这方面的内容,是给线程池来做准备的。BlockingQueue的概念重点是在Blocking上,Blocking阻塞,Queue队列,是阻塞队列。他提供了一系列的方法,我们可以在这些方法的基础之上做到让线程实现自动的阻塞。

我们现在聊的就是这个Queue里面所提供的一些可以给多线程比较友好的接口。他提供了一些什么接口呢,第一个就是offer对应的是原来的那个add,提供了poll取数据,然后提供了peek拿出来这个数据。那么这个是什么意思呢,我们读一下这个offer的概念,offer是往里头添加,加进去没加进去它会给你一个布尔类型的返回值,和原来的add是什么区别呢,add如果加不进去了是会抛异常的。所以一般的情况下我们用的最多的Queue里面都用offer,它会给你一个返回值,peek的概念是去取并不是让你remove掉,poll是取并且remove掉,而且这几个对于BlockingQueue来说也确实是线程安全的一个操作。对于Queue经常用的接口就这么几个,大家了解就可以。

public class T04_ConcurrentQueue {
   public static void main(String[] args) {
      Queue<String> strs = new ConcurrentLinkedQueue<>();
      
      for(int i=0; i<10; i++) {
         strs.offer("a" + i);  //add
      }
      
      System.out.println(strs);
      
      System.out.println(strs.size());
      
      System.out.println(strs.poll());
      System.out.println(strs.size());
      
      System.out.println(strs.peek());
      System.out.println(strs.size());
      
      //双端队列Deque
   }
}

四、BlockingQueue

LinkedBlockingQueue,体现Concurrent的这个点在哪里呢,我们来看这个LinkedBlockingQueue,用链表实现的BlockingQueue,是一个无界队列。就是它可以一直装到你内存满了为止,一直添加。

来看一下这个小程序,这么一些线程,第一个线程是我往里头加内容,加put。BlockingQueue在Queue的基础上又添加了两个方法,这两个方法一个叫put,一个叫take。这两个方法是真真正正的实现了阻塞。put往里装如果满了的话我这个线程会阻塞住,take往外取如果空了的话线程会阻塞住。所以这个BlockingQueue就实现了生产者消费者里面的那个容器。这个小程序是往里面装了100个字符串,a开头i结尾,每装一个的时候睡1秒钟。然后,后面又启动了5个线程不断的从里面take,空了我就等着,什么时候新加了我就马上给它取出来。这是BlockingQueue和Queue的一个基本的概念。

public class T05_LinkedBlockingQueue {

   static BlockingQueue<String> strs = new LinkedBlockingQueue<>();

   static Random r = new Random();

   public static void main(String[] args) {
      new Thread(() -> {
         for (int i = 0; i < 100; i++) {
            try {
               strs.put("a" + i); //如果满了,就会等待
               TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }, "p1").start();

      for (int i = 0; i < 5; i++) {
         new Thread(() -> {
            for (;;) {
               try {
                  System.out.println(Thread.currentThread().getName() + " take -" + strs.take()); //如果空了,就会等待
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         }, "c" + i).start();

      }
   }
}

五、ArrayBlockingQueue

ArrayBlockingQueue是有界的,你可以指定它一个固定的值10,它容器就是10,那么当你往里面扔容器的时候,一旦他满了这个put方法就会阻塞住。然后你可以看看用add方法满了之后他会报异常。offer用返回值来判断到底加没加成功,offer还有另外一个写法你可以指定一个时间尝试着往里面加1秒钟,1秒钟之后如果加不进去它就返回了。

回到那个面试经常被问到的问题,Queue和List的区别到底在哪里,主要就在这里,添加了offer、peek、poll、put、take这些个对线程友好的或者阻塞,或者等待方法。

public class T06_ArrayBlockingQueue {

   static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);

   static Random r = new Random();

   public static void main(String[] args) throws InterruptedException {
      for (int i = 0; i < 10; i++) {
         strs.put("a" + i);
      }
      
      //strs.put("aaa"); //满了就会等待,程序阻塞
      //strs.add("aaa");
      //strs.offer("aaa");
      strs.offer("aaa", 1, TimeUnit.SECONDS);
      
      System.out.println(strs);
   }
}

六、DelayQueue

DelayQueue可以实现在时间上的排序,这个DelayQueue能实现按照在里面等待的时间来进行排序。这里我们new了一个DelayQueue,他是BlockingQueue的一种也是用于阻塞的队列,这个阻塞队列装任务的时候要求你必须实现Delayed接口,Delayed往后拖延推迟,Delayed需要做一个比较compareTo,最后这个队列的实现,这个时间等待越短的就会有优先的得到运行,所以你需要做一个比较 ,这里面他就有一个排序了,这个排序是按时间来排的,所以去做好,哪个时间返回什么样的值,不同的内容比较的时候可以按照时间来排序。总而言之,你要实现Comparable接口重写 compareTo方法来确定你这个任务之间是怎么排序的。getDelay去拿到你Delay多长时间了。往里头装任务的时候首先拿到当前时间,在当前时间的基础之上指定在多长时间之后这个任务要运行,添加顺序参看代码,但是当我们去拿的时候,一般的队列是先加那个先往外拿那个,先进先出。这个队列是不一样的,按时间进行排序(按紧迫程度进行排序)。DelayQueue就是按照时间进行任务调度。

public class T07_DelayQueue {

   static BlockingQueue<MyTask> tasks = new DelayQueue<>();

   static Random r = new Random();
   
   static class MyTask implements Delayed {
      String name;
      long runningTime;
      
      MyTask(String name, long rt) {
         this.name = name;
         this.runningTime = rt;
      }

      @Override
      public int compareTo(Delayed o) {
         if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))
            return -1;
         else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) 
            return 1;
         else 
            return 0;
      }

      @Override
      public long getDelay(TimeUnit unit) {
         
         return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
      }
      
      
      @Override
      public String toString() {
         return name + " " + runningTime;
      }
   }

   public static void main(String[] args) throws InterruptedException {
      long now = System.currentTimeMillis();
      MyTask t1 = new MyTask("t1", now + 1000);
      MyTask t2 = new MyTask("t2", now + 2000);
      MyTask t3 = new MyTask("t3", now + 1500);
      MyTask t4 = new MyTask("t4", now + 2500);
      MyTask t5 = new MyTask("t5", now + 500);
      
      tasks.put(t1);
      tasks.put(t2);
      tasks.put(t3);
      tasks.put(t4);
      tasks.put(t5);
      
      System.out.println(tasks);
      
      for(int i=0; i<5; i++) {
         System.out.println(tasks.take());
      }
   }
}

DelayQueue本质上用的是一个PriorityQueue,PriorityQueue是从AbstractQueue继承的。PriorityQueue特点是它内部你往里装的时候并不是按顺序往里装的,而是内部进行了一个排序。按照优先级,最小的优先。它内部实现的结构是一个二叉树,这个二叉树可以认为是堆排序里面的那个最小堆值排在最上面。

public class T07_01_PriorityQueque {
    public static void main(String[] args) {
        PriorityQueue<String> q = new PriorityQueue<>();

        q.add("c");
        q.add("e");
        q.add("a");
        q.add("d");
        q.add("z");

        for (int i = 0; i < 5; i++) {
            System.out.println(q.poll());
        }

    }
}

七、SynchronousQueue

SynchronousQueue容量为0,就是这个东西它不是用来装内容的,SynchronousQueue是专门用来两个线程之间传内容的,给线程下达任务的,之前讲过一个容器叫Exchanger,本质上这个容器的概念是一样的。看下面代码,有一个线程起来等着take,里面没有值一定是take不到的,然后就等着。然后当put的时候能取出来,take到了之后能打印出来,最后打印这个容器的size一定是0,打印出aaa来这个没问题。那当把线程注释掉,在运行一下程序就会在这阻塞,永远等着。如果add方法直接就报错,原因是满了,这个容器为0,你不可以往里面扔东西。这个Queue和其他的很重要的区别就是你不能往里头装东西,只能用来阻塞式的put调用,要求是前面得有人等着拿这个东西的时候你才可以往里装,但容量为0,其实说白了就是我要递到另外一个的手里才可以。这个SynchronousQueue看似没有用,其实不然,SynchronousQueue在线程池里用处特别大,很多的线程取任务,互相之间进行任务的一个调度的时候用的都是它。

public class T08_SynchronusQueue { //容量为0
   public static void main(String[] args) throws InterruptedException {
      BlockingQueue<String> strs = new SynchronousQueue<>();
      
      new Thread(()->{
         try {
            System.out.println(strs.take());
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }).start();

      strs.put("aaa"); //阻塞等待消费者消费
      //strs.put("bbb");
      //strs.add("aaa");
      System.out.println(strs.size());
   }
}

八、TransferQueue

TransferQueue传递,实际上是前面这各种各样Queue的一个组合,它可以给线程来传递任务,以此同时不像是SynchronousQueue只能传递一个,TransferQueue做成列表可以传好多个。比较牛X的是它添加了一个方法叫transfer,如果我们用put就相当于一个线程来了往里一装它就走了。transfer就是装完在这等着,阻塞等有人把它取走我这个线程才回去干我自己的事情。一般使用场景:是我做了一件事情,我这个事情要求有一个结果,有了这个结果之后我可以继续进行我下面的这个事情的时候,比方说我付了钱,这个订单我付账完成了,但是我一直要等这个付账的结果完成才可以给客户反馈。

public class T09_TransferQueue {
   public static void main(String[] args) throws InterruptedException {
      LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();
      
      new Thread(() -> {
         try {
            System.out.println(strs.take());
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }).start();
      
      strs.transfer("aaa");
      
      //strs.put("aaa");

      /*new Thread(() -> {
         try {
            System.out.println(strs.take());
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }).start();*/
   }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/633812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详细讲解!selenium:解决页面元素display:none的方法

目录 前言&#xff1a; 1、具体问题 2、解决方案 代码解析&#xff1a; 结尾&#xff1a; 前言&#xff1a; 在进行 Web 自动化测试时&#xff0c;页面元素的可见性对测试结果的准确性和稳定性至关重要。然而&#xff0c;有些时候页面元素会被设置为 display:none&#x…

亚马逊云科技出海日,助推出海业务全球拓展突飞猛进

出海路漫漫&#xff0c;企业开拓全球市场而孤军奋战&#xff0c;常常会感到力不从心。好的产品有了&#xff0c;渠道有了&#xff0c;供应链有了&#xff0c;还要自己从0-1搭建存储、网络和架构&#xff1f;营销季来了想趁机冲一波销量&#xff0c;还要自己运维本地IDC、大促来…

10.无监督学习之K-means算法

10.1 无监督学习的定义 监督学习&#xff1a;我们有一些列标签&#xff0c;然后用假设函数去拟合它 无监督学习&#xff1a;给出的数据不带任何标签。对于无监督学习来说&#xff0c;需要做的就是将数据输入到算法中&#xff0c;让算法找到一些隐含在数据中的结构&#xff0c;通…

C语言—程序环境和预处理

程序环境和预处理 程序的翻译环境和执行环境编译、链接翻译环境编译预处理&#xff08;预编译&#xff09;编译汇编 链接 编译环境几个阶段的总结 运行环境&#xff08;执行环境&#xff09;预处理详解预定义符号#define#define 定义标识符#define 定义宏#define 替换规则#和##…

【MySQL】数据库中表的操作详解

【MySQL】数据库表的基本操作 一、表的创建二、查看表结构三、修改表结构3.1 添加表中字段3.2 修改表中字段3.3 删除表中字段3.4 修改表名3.5 修改列名 四、删除表 温馨提示&#xff1a;这里的表操作指的是表结构的操作&#xff0c;属于DDL数据定义语言 一、表的创建 CREATE …

hooks组件+例子+底层机制

1.React组件分类 函数组件 1.不具备"状态、ref、周期函数"等内容&#xff0c;第一次渲染完毕后&#xff0c;无法基于组件内部的操作来控制其更新&#xff0c;因此称之为静态组件!。但是具备属性及插槽&#xff0c;父组件可以控制其重新渲染 2.渲染流程简单&#xff…

Same Symbol | 哇咔咔!!!盘点一下表达矩阵中重复基因的处理方法!~

1写在前面 医院天天叫我们填问卷&#xff0c;我真是不能理解。&#x1fae0; 动不动就问我们对医院的福利满意吗&#xff0c;对自己的收入满意吗&#xff0c;觉不觉得工作负荷太重了&#xff1f;&#xff1f;&#xff1f;&#x1f642; 我们满不满意&#xff0c;觉不觉得累&…

大学物理(上)-期末知识点结合习题复习(2)——运动的描述考点总结、质点运动学-牛顿运动定律

目录 运动的描述 期末考点 质点运动学 牛顿运动定律知识点 题1(牛顿第二定律) 题目描述 题解 题2 (圆周运动) 题目描述 题解 运动的描述 期末考点 1.速度和加速度的推导 平均速度平均速度反映的只是在一段时间内位移的变化&#xff0c;如果需要精准的地知道质点在…

chatgpt赋能python:Python如何判断奇偶数?

Python如何判断奇偶数&#xff1f; 作为一门功能强大且容易上手的编程语言&#xff0c;Python具有许多有用的工具和功能。其中之一就是判断奇偶数。在本文中&#xff0c;我们将介绍Python中判断奇偶数的不同方法。 H1&#xff1a;Python中的基本判断方法 Python中最基本的判…

(八)CSharp-泛型协变和逆变(3)

一、协变和逆变 可变性分为三种&#xff1a; 协变、逆变和不变。 协变和逆变&#xff1a; 为泛型接口和泛型委托添加了一个处理类型转换问题的扩展。 问题&#xff1a; 当两个类对象是继承与派生的关系时&#xff0c;由于编译器通过泛型实例化时没法确认它们之间的关系&…

(数组) 1991. 找到数组的中间位置 ——【Leetcode每日一题】

❓1991. 找到数组的中间位置 难度&#xff1a;简单 给你一个下标从 0 开始的整数数组 nums &#xff0c;请你找到 最左边 的中间位置 middleIndex &#xff08;也就是所有可能中间位置下标最小的一个&#xff09;。 中间位置 middleIndex 是满足 nums[0] nums[1] ... num…

FTP协议详解

文章目录 1 FTP概述2 实验环境3 FTP详解3.1 文件传输过程3.2 报文格式3.3 数据连接3.4 主动模式3.5 被动模式3.6 匿名服务器 4 总结 1 FTP概述 FTP为File Transfer Protocol的缩写&#xff0c;即文件传输协议&#xff0c;是TCP/IP 协议族中的协议之一。FTP是一个用于在计算机网…

算法模板(3):搜索(3):图论提高

图论提高 最小生成树 &#xff08;1&#xff09;朴素版prim算法&#xff08; O ( n 2 ) O(n ^ 2) O(n2)&#xff09; 适用范围&#xff1a;稠密图易错&#xff1a;注意有向图还是无向图&#xff1b;注意有没有重边和负权边。从一个集合向外一个一个扩展&#xff0c;最开始只…

(文章复现)面向配电网韧性提升的移动储能预布局与动态调度策略(1)-灾前布局matlab代码

参考文献&#xff1a; [1]王月汉,刘文霞,姚齐,万海洋,何剑,熊雪君.面向配电网韧性提升的移动储能预布局与动态调度策略[J].电力系统自动化,2022,46(15):37-45. 1.基本原理 1. 1 目标函数 本文以最恶劣光伏出力场景下的移动储能配置成本与负荷削减成本最小为目标&#xff0c;建…

(数组) 724. 寻找数组的中心下标 ——【Leetcode每日一题】

❓724. 寻找数组的中心下标 难度&#xff1a;简单 给你一个整数数组 nums &#xff0c;请计算数组的 中心下标 。 数组 中心下标 是数组的一个下标&#xff0c;其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端&#xff0c;那么左侧数之和视为…

2023-6-9

1.网络训练&#xff1a; 在训练前先要看看读取数据的时间&#xff08;常见的性能瓶颈&#xff09;2.import dis dis 是 Python 内置的一个模块&#xff0c;其全称为 “Disassembler for Python bytecode”&#xff0c;用于反汇编 Python 字节码。它可以将 Python 代码编译成字…

视频换天造物实践秒变科幻大片实践记录

视频换天造物实践秒变科幻大片实践记录&#xff0c;过程中遇到些坑&#xff0c;结果还是相当震撼 预装软件&#xff1a; matplotlib scikit-image scikit-learn scipy numpy torch torchvision opencv-python opencv-contrib-python 安装使用的时候可能碰上scikit-image 新版…

傅里叶级数简介

先看动图 将函数f(x) 用 sin(nx) cos(nx) 的形式表示出来的方式就是傅里叶级数 这里有几个使用条件 收敛性&#xff1a;符合迪力克雷收敛条件。简单理解为 f(x) 必须是一个丝滑的曲线。周期性&#xff1a; f(x) 必须是一个周期函数 还有一个基础条件&#xff0c;三角函数具…

element-plus布局排版问题总结(更新ing)

文章目录 el-container空隙修改app组件 el-container空隙 源码-更改了容器的显示&#xff0c;占满屏幕 <template><div class"common-layout"><el-container><el-header><el-row class"el-row1"><el-col :span"12&…

oppo r11 升级8.1系统 图文教程

Time: 2023年6月11日13:39:25 By:MemroyErHero 1 预留一定的空间,存放刷机包. 2 导入刷机包 r11.ozip 到手机上 3 手机文件管理器 打开 r11.ozip 文件 4 点击立即更新即可 5 重要的事情说三遍,刷机过程中 不能关机 不能断电 否则会变成砖头 重要的事情说三遍,刷机过程中 …