高并发编程:并发容器

这篇具有很好参考价值的文章主要介绍了高并发编程:并发容器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、概述

 

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

高并发编程:并发容器

二、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就是装完在这等着,阻塞等有人把它取走我这个线程才回去干我自己的事情。一般使用场景:是我做了一件事情,我这个事情要求有一个结果,有了这个结果之后我可以继续进行我下面的这个事情的时候,比方说我付了钱,这个订单我付账完成了,但是我一直要等这个付账的结果完成才可以给客户反馈。文章来源地址https://www.toymoban.com/news/detail-479260.html

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

到了这里,关于高并发编程:并发容器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 并发容器11

    JDK 提供的这些容器大部分在 java.util.concurrent 包中。 ConcurrentHashMap: 线程安全的 HashMap CopyOnWriteArrayList: 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector. ConcurrentLinkedQueue: 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列

    2024年02月10日
    浏览(31)
  • 标准库容器概述

    名称 类型 插入性能 删除性能 查找性能 何时使用 vector 顺序 平均 O(1) 在末尾;否则 O(N) 平均 O(1) 在末尾;否则 O(N) O(1) 默认容器。使用其他容器前先用分析器确认 list 顺序 O(1) 在开始和结束处,以及插入位置 O(1) 在开始和结束处,以及删除位置 O(1) 访问第一个或最后一个元素

    2024年01月21日
    浏览(27)
  • 线程系列 7 - JUC高并发容器类

               早期的同步容器一般使用 Vector、HashTable、java.util.Collections ,这些同步容器实现线程安全的方式是:在需要同步访问的方法上添加synchronized。        synchronized 在线程没有发生争用的场景下处于偏向锁的状态,其性能是非常高的。但是,一旦发生了线

    2024年02月15日
    浏览(49)
  • 基于Java+SpringBoot+vue前后端分离疫情下图书馆管理系统设计实现

    博主介绍 : ✌ 全网粉丝30W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ 🍅 文末获取源码联系 🍅 👇🏻 精彩专栏 推荐订阅 👇🏻 不然下次找不到哟 2022-2024年

    2024年02月13日
    浏览(59)
  • 并发编程基础知识篇--并发编程的优点&缺点

    目录 并发编程的优点缺点 为什么要使用并发编程(优点): 并发编程的缺点: 频繁的上下文切换 线程安全 易混淆的概念 阻塞与非阻塞 阻塞模型 非阻塞模型 同步与异步 同步调用 异步调用 临界区 并发与并行 上下文切换 并发编程是指在程序中同时执行多个独立的任务或操

    2024年02月11日
    浏览(47)
  • 【并发编程】JUC并发编程(彻底搞懂JUC)

    如果你对多线程没什么了解,那么从入门模块开始。 如果你已经入门了多线程(知道基础的线程创建、死锁、synchronized、lock等),那么从juc模块开始。 新手学技术、老手学体系,高手学格局。 JUC实际上就是我们对于jdk中java.util .concurrent 工具包的简称 ,其结构如下: 这个包

    2024年02月20日
    浏览(52)
  • 常见网络服务器并发模型

    近些年,随着互联网的大发展,高并发服务器技术也快速进步,从简单的循环服务器模型处理少量网络并发请求,演进到解决C10K,C10M问题的高并发服务器模型。本文主要以TCP为例,总结了几种常见的网络服务器模型的实现方式,优缺点,以及应用实例。 单线程循环 单线程循

    2024年02月08日
    浏览(51)
  • GO语言网络编程(并发编程)并发介绍,Goroutine

    进程和线程 并发和并行 协程和线程 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。 线程:一个线程上可以跑多个协程,协程是轻量级的线程。 goroutine 只是由官方实现的超级\\\"线程池\\\"。 每个

    2024年02月09日
    浏览(58)
  • 安全无忧:Java并发集合容器的应用与实践

    JDK 提供的这些容器大部分在 java.util.concurrent 包中: ConcurrentHashMap : 线程安全的 HashMap CopyOnWriteArrayList : 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector ConcurrentLinkedQueue : 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列

    2024年01月19日
    浏览(45)
  • 并发编程(高并发、多线程)

    1.1.1 并发编程三要素 首先我们要了解并发编程的三要素 原子性 可见性 有序性 1.原子性 原子性是指一个操作是不可分割的单元,要么全部执行成功,要么全部失败。 在并发环境中,多个线程可能同时访问和修改共享的数据,为了确保数据的一致性,需要确保一组相关的操作

    2024年02月04日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包