精华内容
下载资源
问答
  • 、同步类容器 1. 为什么会出现同步容器? 2. Java中的同步容器类 3. 同步类容器的缺陷 、并发类容器 1. ConcurrentHashMap 引申1:锁的粒度 引申2:用ConcurrentHashMap实现本地缓存 2. Copy-On-Write...

    目录

     

    一、同步类容器

    1. 为什么会出现同步容器?

    2. Java中的同步容器类

    3. 同步类容器的缺陷

    二、并发类容器

    1. ConcurrentHashMap

    引申1:锁的粒度

    引申2:用ConcurrentHashMap实现本地缓存

    2. Copy-On-Write

    3. 并发Queue

    1)ConcurrentLinkedQueue

    2)BlockingQueue


    一、同步类容器

     为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列、Synchronizer(比如CountDownLatch)。今天我们就来讨论下同步容器和并发类容器。

    1. 为什么会出现同步容器?

      在Java的集合容器框架中,主要有四大类别:List、Set、Queue、Map。

      List、Set、Queue接口分别继承了Collection接口,Map本身是一个接口。

      注意Collection和Map是一个顶层接口,而List、Set、Queue则继承了Collection接口,分别代表数组、集合和队列这三大类容器。

      像ArrayList、LinkedList都是实现了List接口,HashSet实现了Set接口,而Deque(双向队列,允许在队首、队尾进行入队和出队操作)继承了Queue接口,PriorityQueue实现了Queue接口。另外LinkedList(实际上是双向链表)实现了了Deque接口。

      像ArrayList、LinkedList、HashMap这些容器都是非线程安全的。

      如果有多个线程并发地访问这些容器时,就会出现问题。

      因此,在编写程序时,必须要求程序员手动地在任何访问到这些容器的地方进行同步处理,这样导致在使用这些容器的时候非常地不方便。

      所以,Java提供了同步容器供用户使用。

    2. Java中的同步容器类

      在Java中,同步容器主要包括2类:

      1)Vector、Stack、HashTable

      2)Collections类中提供的静态工厂方法创建的类

      Vector矢量队列实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。

      Stack栈也是一个同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类。

      HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。

      Collections类是一个工具提供类,注意,它和Collection不同,Collection是一个顶层的接口。在Collections类中提供了大量的方法,比如对集合或者容器进行排序、查找等操作。最重要的是,在它里面提供了几个静态工厂方法来创建同步容器类,如下图所示:

    3. 同步类容器的缺陷

            首先,从同步容器的具体实现源码可知,同步容器中的方法采用了synchronized进行了同步,显然这必然会影响到执行性能。举个例子,进行同样多的插入操作,Vector的耗时是ArrayList的两倍。另外,由于Vector中的add方法和get方法都进行了同步,因此,在有多个线程进行访问时,如果多个线程都只是进行读取操作,那么每个时刻就只能有一个线程进行读取,其他线程便只能等待,这些线程必须竞争同一把锁。

            其次,同步类容器并不是所有操作都是线程安全的。同样以Vectory为例,他只能包装单个方法的操作是同步的,但在长度发生变化时,很容易抛数组下标越界异常ArrayIndexOutOfBoundsException。举个例子:

    //线程1
    for(int i=0;i<vector.size();i++)
        vector.get(i);
    //线程2
    for(int i=0;i<vector.size();i++)
        vector.remove(i);

    假设vectory的大小为10,由于仅仅对vectory的方法做了同步,

    那么在i=9时,线程1在获取size之后释放锁

    线程2刚好也循环到了i=9的时候,它拿到锁,删除了index=9的数据并释放锁

    那么线程1中的get方法拿到锁,要删除index=9的数据,然而这个时候数组最大的index为8,抛出异常。

    二、并发类容器

      很明显,由于同步类容器的状态都是串行化的,虽然实现了线程安全,但是严重降低了并发性,在多线程环境时,会严重影响应用程序的吞吐量,并不能满足我们当今互联网时代高并发的需求。

    在Java 1.5中给我们提供了多种并发容器来替代同步类容器从而改善性能,位于java.util.concurrent目录下。

    使用ConcurrentHashMap来代替基于散列的传统HashTable,而且添加了一些常见符合操作的支持。

    使用了CopyOnWriteArrayList代替了Voctor

    并发的CopyOnWriteArraySet

    并发队列ConcurrentLinkedQueue和LinkedBlockingQueue,前者是高性能队列,后者是阻塞队列,还有更多其他的队列等。

    相比同步类容器,并发类容器主要解决了两个问题: 
    (1) 根据具体场景进行设计,尽量避免synchronized,提供并发性。 
    (2) 定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。

    下面我详细讲解下其中的几个类

    1. ConcurrentHashMap

    ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类Hash Table的结构,Segment内部维护了一个链表数组,我们用下面这一幅图来看下ConcurrentHashMap的内部结构:

    å¾è¡¨1

     从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上),所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

    具体细节分析大家有兴趣可以看:ConcurrentHashMap之实现细节

    引申1:锁的粒度

    上面说到这种策略就是很典型的通过减小锁的粒度来提升并发性能,也可以称之为锁分离。可能有人对锁的粒度这个词不是很了解,我在这里给大家做个形象的比喻。

    为什么要加锁?加锁是为了防止不同的线程访问同一共享资源造成混乱                                                                                        打个比方:人是不同的线程,卫生间是共享资源
    你在上洗手间的时候肯定要把门锁上吧,这就是加锁,只要你在里面,这个卫生间就被锁了,只有你出来之后别人才能用。的的
    什么是加锁粒度呢?所谓加锁粒度就是你要锁住的范围是多大。
    比如你在家上卫生间,你只要锁住卫生间就可以了吧,不需要将整个家都锁起来不让家人进门吧,卫生间就是你的加锁粒度。
    怎样才算合理的加锁粒度呢?
    其实卫生间并不只是用来上厕所的,还可以洗澡,洗手。这里就涉及到优化加锁粒度的问题。
    你在卫生间里洗澡,其实别人也可以同时去里面洗手,只要做到隔离起来就可以,如果马桶,浴缸,洗漱台都是隔开相对独立的,实际上卫生间可以同时给三个人使用,
    当然三个人做的事儿不能一样。这样就细化了加锁粒度,你在洗澡的时候只要关上浴室的门,别人还是可以进去洗手的。如果当初设计卫生间的时候没有将不同的功能区域划分隔离开,就不能实现卫生间资源的最大化使用。这就是设计架构的重要性。

    引申2:用ConcurrentHashMap实现本地缓存

    基于ConcurrentHashMap的特性,我们可以用它来实现java本地缓存,来缓存类似黑名单,配置,城市列表,网站协议等等读多写少的数据,减少对数据库的访问。

    public final class Cache {	
    	/**
    	 * 预缓存信息
    	 */
    	private static final Map<String, Object> CACHE_MAP = new ConcurrentHashMap<String, Object>();
    	/**
    	 * 每个缓存生效时间12小时
    	 */
    	public static final long CACHE_HOLD_TIME_12H = 12 * 60 * 60 * 1000L;	
    	/**
    	 * 每个缓存生效时间24小时
    	 */
    	public static final long CACHE_HOLD_TIME_24H = 24 * 60 * 60 * 1000L;
    	
    	/**
    	 * 存放一个缓存对象,默认保存时间12小时
    	 * @param cacheName
    	 * @param obj
    	 */
    	public static void put(String cacheName, Object obj) {
    		put(cacheName, obj, CACHE_HOLD_TIME_12H);
    	}
    	
    	/**
    	 * 存放一个缓存对象,保存时间为holdTime
    	 * @param cacheName
    	 * @param obj
    	 * @param holdTime
    	 */
    	public static void put(String cacheName, Object obj, long holdTime) {
    		CACHE_MAP.put(cacheName, obj);
    		CACHE_MAP.put(cacheName + "_HoldTime", System.currentTimeMillis() + holdTime);//缓存失效时间
    	}
    	
    	/**
    	 * 取出一个缓存对象
    	 * @param cacheName
    	 * @return
    	 */
    	public static Object get(String cacheName) {
    		if (checkCacheName(cacheName)) {
    			return CACHE_MAP.get(cacheName);
    		}
    		return null;
    	}
    	
    	/**
    	 * 删除所有缓存
    	 */
    	public static void removeAll() {
    		CACHE_MAP.clear();
    	}
    	
    	/**
    	 * 删除某个缓存
    	 * @param cacheName
    	 */
    	public static void remove(String cacheName) {
    		CACHE_MAP.remove(cacheName);
    		CACHE_MAP.remove(cacheName + "_HoldTime");
    	}
    	
    	/**
    	 * 检查缓存对象是否存在,
    	 * 若不存在,则返回false
    	 * 若存在,检查其是否已过有效期,如果已经过了则删除该缓存并返回false
    	 * @param cacheName
    	 * @return
    	 */
    	public static boolean checkCacheName(String cacheName) {
    		Long cacheHoldTime = (Long) CACHE_MAP.get(cacheName + "_HoldTime");
    		if (cacheHoldTime == null || cacheHoldTime == 0L) {
    			return false;
    		}
    		if (cacheHoldTime < System.currentTimeMillis()) {
    			remove(cacheName);
    			return false;
    		}
    		return true;
    	}
    }
    

    但是,如果要用它来代替mamecache或者redis或者tair来实现java本地缓存的话,还缺少一个缓存策略。最经典的缓存策略就是LRU(Least Recently Used )最近较少使用的缓存淘汰策略。可以使用ConcurrentHashMap与LinkedHashMap协同实现,或者直接使用Apache下的LRUMap来实现,具体实现方法我在之后的博客中会进行整理说明,这里暂不阐述。

    2. Copy-On-Write

    CopyOnWrite 容器又称COW,即写时复制容器,原理就是当我们往一个容器添加元素时,不直接往当前容器添加,而是先将当前容器进行Copy,复制出新的容器,然后往新的容器进行添加元素,添加好之后,再将原来的容器引用指向新的容器。 
    这样的好处就是,我们可以对CopyOnWrite容器进行并发的读,也不需要加锁,因为当前容器不会添加任何新的元素。
    同时,代码底层通过加锁的方式,解决了多个写进程之间的同步问题。

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

    CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。实现代码如下:

    public class BlackListServiceImpl {
     
        private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>(
                1000);
     
        public static boolean isBlackList(String id) {
            return blackListMap.get(id) == null ? false : true;
        }
     
        public static void addBlackList(String id) {
            blackListMap.put(id, Boolean.TRUE);
        }
     
        /**
         * 批量添加黑名单
         *
         * @param ids
         */
        public static void addBlackList(Map<String,Boolean> ids) {
            blackListMap.putAll(ids);
        }
     
    }

     代码很简单,但是使用CopyOnWriteMap需要注意两件事情:

      1. 减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。

      2. 使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。如使用上面代码里的addBlackList方法。

    CopyOnWrite的缺点:

      内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。

      针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap

      数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

    3. 并发Queue

    在并发队列上,JDK提供了两套实现,两者都实现或者说继承了Queue接口

    1)ConcurrentLinkedQueue

    ConcurrentLinkedQueue是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能。通常来说,他都性能会优于BlockingQueue。

    ConcurrentLinkedQueue是一个机遇链接节点的无界线程安全队列。该队列的元素准讯先进先出的原则,头是最先加入的,尾是最近加入的,不允许有null元素,结构如下

     ConcurrentLinkedQueue由head节点和tair节点组成,每个节点(Node)由节点元素(item)和指向下一个节点的引用(next)组成,节点与节点之间就是通过这个next关联起来,从而组成一张链表结构的队列。默认情况下head节点存储的元素为空,tair节点初始化等于head节点。

    入队列

    将入队节点设置成当前队列尾节点的下一个节点。第二是更新tail节点,如果tail节点的next节点不为空,则将入队节点设置成tail节点,如果tail节点的next节点为空,则将入队节点设置成tail的next节点,所以tail节点不总是尾节点,理解这一点对于我们研究源码会非常有帮助。

    上面的分析让我们从单线程入队的角度来理解入队过程,但是多个线程同时进行入队情况就变得更加复杂,因为可能会出现其他线程插队的情况。如果有一个线程正在入队,那么它必须先获取尾节点,然后设置尾节点的下一个节点为入队节点,但这时可能有另外一个线程插队了,那么队列的尾节点就会发生变化,这时当前线程要暂停入队操作,然后重新获取尾节点。让我们再通过源码来详细分析下它是如何使用CAS算法来入队的。

    出队列

    将出队节点从队列移除

    åºéå

    从上图可知,并不是每次出队时都更新head节点,当head节点里有元素时,直接弹出head节点里的元素,而不会更新head节点。只有当head节点里没有元素时,出队操作才会更新head节点。这种做法也是通过hops变量来减少使用CAS更新head节点的消耗,从而提高出队效率。

    由于博主本人暂时不了解CAS执行过程,有兴趣的朋友可以移步去看原文:

    聊聊并发(六)ConcurrentLinkedQueue的实现原理分析

    2)BlockingQueue

    • ArrayBlockingQueue

    基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。

    • LinkedBlockingQueue

    基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。

    • SynchronousQueue

    SynchronousQueue是无界的,是一种无缓冲的等待队列,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加;可以认为SynchronousQueue是一个缓存值为1的阻塞队列。

     

    阻塞队列中的常用方法:

           put方法用来向队尾存入元素,如果队列满,则等待;

      take方法用来从队首取元素,如果队列为空,则等待;

      offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;

      poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;

    应用场景:

    在一个高并发的环境下,推荐使用ArrayBlockingQueue来限制队列中的请求,防止无界队列可能导致的内存耗尽问题。

    当并发不是非常严重时,可以使用LinkedBlockingQueue,提高请求处理的实时性

    当并发量很低是,可以使用SynchronousQueue,请求直接提交到生产者(基本很少发生)

    • PriorityBlockingQueue

    基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,必须实现Comparable接口),基于对象的compareTo方法判断优先级(-1,0,1),越小越先出队。此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),不支持null值。

    • DelayQueue

    DelayQueue 是一个支持延时获取元素的无界阻塞队列,队列使用PriorityQueue来实现,队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素,只有在延时期满时才能从队列中提取元素。 由于使用PriorityQueue来实现,传入的对象必须实现Comparable接口。

    使用场景参考下图

     

    PS:博文仅作为个人学习笔记,如有错误欢迎指正,转载请注明出处~

    详见:笔记分类导航目录

    参考文档:

    ps:海大大好牛,好几次搜到的资料都是他写的!

    1. Java并发编程:同步容器                   

    2. 锁及锁粒度的详细比喻

    3. 聊聊并发(六)ConcurrentLinkedQueue的实现原理分析

    展开全文
  • 、同步类容器 如古老的Vector、HashTable。这些容器的同步功能其实都是有JDK的Collections.synchronized**等工厂方法去创建的。其底层的机制无非就是用传统的synchronized关键字对每个公用的方法都进行同步,使得...

    一、同步类容器

    如古老的Vector、HashTable。这些容器的同步功能其实都是有JDK的Collections.synchronized**等工厂方法去创建的。其底层的机制无非就是用传统的synchronized关键字对每个公用的方法都进行同步,使得每次只能有一个线程访问容器的状态。这很明显不满足我们今天互联网时代高并发的需求,在保证线程安全的同时,也必须要有足够好的性能。

    二、并发类容器

    2.1 ConcurrentMap

    ConcurrentMap接口有两个重要的实现:

    ConcurrentHashMap

    ConcurrentSkipListMap(支持并发排序功能,弥补ConcurrentHashMap)对应TreeMap

    2.1.1 ConcurrentHashMap

    ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个其实就是一个小的HashTable。它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。把一个整体分成了16个段(Segment)。也就是最高支持16个线程的并发修改操作。这也是在多线程场景时减小锁的粒度从而降低锁竞争的一种方案。并且代码中大多共享变量使用volatile关键字声明,目的是第一时间获取修改的内容,性能非常好。

    public class UseConMap {
    	public static void main(String[] args) {
    
    		ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
    		map.put("k1", "v1");
    		map.put("k2", "v2");
    		map.put("k3", "v3");
    
    		map.putIfAbsent("k4", "v4");
    
    		for (Map.Entry<String, String> entry : map.entrySet()) {
    			System.out.println(entry.getKey() + ": " + entry.getValue());
    		}
    	}
    }

    2.1.2 ConcurrentSkipListMap

     

    2.2 Copy-On-Write容器

    CopyOnWrite容器即写时复制的容器。通俗的理解是当我们对一个容器(添加、修改、删除)的时候,不直接往当前容器(添加、修改、删除),而是先将当前容器进行copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

    适合读多写少的场景。

    如果写多,那么就会频繁的拷贝,消耗性能。写多,不如直接在代码上加锁。

    2.2.1 CopyOnWriteArrayList

     

    2.2.2 CopyOnWriteArraySet

     

    展开全文
  • 、同步类容器的种类 1、Vector Vector实现了List接口,Vector实际上就是个数组,ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。...、同步类容器的缺陷 在对同步类...

    一、同步类容器的种类

    1、Vector

    Vector实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。

    2、HashTable

    HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。

    二、同步类容器的缺陷

    在对同步类容器进行迭代修改是,会抛出ConcurrentModificationException异常。

    参考链接:https://www.cnblogs.com/dolphin0520/p/3933404.html

    三、并发类容器

    1、ConcurrentHashMap

    ConcurrentMap接口有两个重要实现:ConcurrentHashMap和ConcurrentSkipListMap(支持并发排序功能,弥补了ConcurrentHashMap)。

    ConcurrentHashMap内部使用段(Segment)来表示不同的部分,每一个段其实是一个小的HashTable,它们有自己的锁。只要修改操作发生在不同的段上,就可以实现并发操作。一个整体分成16个段,最高支持16个线程的并发修改操作。通过减少锁的粒度从而降低锁竞争。代码中大量使用volatile关键字声明,可以第一时间获取修改内容,性能很好。

    2、Copy-On-Write简称COW

    JDK中COW容器有两种:CopyOnWriteArrayList和CopyOnWriteArraySet。

    什么是CopyOnWrite容器?

    当往容器中添加元素的时候,不直接向当前容器中添加元素,而是复制一份一模一样的当前容器,在新的容器里面添加元素,添加完元素后,将原容器的引用指向新的容器。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁。CopyOnWrite容器是一种读写分离的思想。     该容器适合于读多写少的场景。

    非阻塞队列

    1、ConcurrentLinkedQueue

    ConcurrentLinkedQueue是一种适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue。它是一个基于链接节点的无界线程安全队列,遵循先进先出的原则,该队列不允许null元素。

    ConcurrentLinkedQueue重要方法:

    add()和offer()都是加入元素的方法(在ConcurrentLinkedQueue,这两个方法没区别)。

    poll()和peek()都是取头元素结点,区别在于poll()会删除元素,peek()不会删除元素。

    阻塞队列

    1、ArrayBlockingQueue

    基于数组实现的阻塞队列,在ArrayBlockingQueue内部维护着一个定长的数组,来缓存队列中的数据对象,内部没有实现读写分离,也就意味着生产和消费不能完全并行,长度是需要定义的,可以指定先进先出或先进后出,也叫有界队列。

    2、LinkedBlockingQueue

    基于链表的阻塞队列,LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者完全并行。它是一种无界队列。

    3、SynchronousQueue

    没有缓冲的队列,生产者生产的数据会被消费者直接消费。

    4、PriorityBlockingQueue(调用take方法的时候会对队列中的元素进行排序)

    基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,传入队列的对象必须实现Comparable接口),实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,它是一种无界队列。

    5、DelayQueue

    带有延迟时间的队列,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取元素。DelayQueue中的元素必须实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。

    6、在阻塞队列中put、offer、add三种方法的区别

    offer:如果队列满了直接返回false

    put:如果队列满了会阻塞,直到收到通知

    add:1、如果队列满了直接返回(不阻塞)直接报错 2、内部实际上调用的是offer方法

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 并发类容器-第

    2019-06-25 23:36:00
    、首先我们来熟悉一下跳表(数据结构算法) 参考资料: https://www.cnblogs.com/seniusen/p/9870398.html https://blog.csdn.net/u010425776/article/details/54890215 跳表的由来:作用:存储有序序列,...

    一、首先我们来熟悉一下跳表(数据结构和算法)

    参考资料:

    https://www.cnblogs.com/seniusen/p/9870398.html

    https://blog.csdn.net/u010425776/article/details/54890215

    跳表的由来:
    作用:存储有序序列,并且实现高效的查找与插入删除。
    存储有序序列最简单的办法就是使用数组,从而查找可以采用二分搜索,但插入删除需要移动元素较为低效。
    因此出现了二叉搜索树,用来解决插入删除移动元素的问题。但二叉搜索树在最坏情况下会退化成一条单链表,搜索的效率降为O(n)。
    为了避免二叉搜索树的退化,出现了二叉平衡树,它在每次插入删除节点后都会重新调整树形,使得它仍然保持平衡,从而保证了搜索效率,也保证了插入删除的效率。
    此外,根据平衡算法的不同,二叉平衡树又分为:B+树、B-树、红黑树。
    但平衡算法过于复杂,因此出现跳表。

    二、ConcurrentSkipListMap 分析

    参考文献:

    https://www.cnblogs.com/skywang12345/p/3498556.html(比较详细)

    简单总结:

    ConcurrentSkipListMap是线程安全的有序的哈希表,适用于高并发的场景。
    ConcurrentSkipListMap和TreeMap,它们虽然都是有序的哈希表。

           但是,第一,它们的线程安全机制不同,TreeMap是非线程安全的,而ConcurrentSkipListMap是线程安全的。第二,ConcurrentSkipListMap是通过跳表实现的,而TreeMap是通过红黑树实现的。
    关于跳表(Skip List),它是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。

    三、ConcurrentSkipListSet

    参考文献:

    https://www.cnblogs.com/duanxz/p/3549633.html(还算是比较详细)

    简单总结:

    ConcurrentSkipListSet是线程安全的有序的集合,适用于高并发的场景。
    ConcurrentSkipListSet和TreeSet,它们虽然都是有序的集合。但是,第一,它们的线程安全机制不同,TreeSet是非线程安全的,而ConcurrentSkipListSet是线程安全的。第二,ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,而TreeSet是通过TreeMap实现的。

     

    四、ConcurrentLinkedQueue

    无界非阻塞队列,底层是个链表,遵循先进先出原则。

    add,offer将元素插入到尾部,peek(拿头部的数据,但是不移除)和poll(拿头部的数据,但是移除)

    五、写时复制容器 (CopyOnWriteArrayList、CopyOnWriteArraySet

    写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以写时复制容器也是一种读写分离的思想,读和写不同的容器。如果读的时候有多个线程正在向容器添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的只能保证最终一致性

    适用读多写少的并发场景,常见应用:白名单/黑名单, 商品类目的访问和更新场景

    存在内存占用问题。

    转载于:https://www.cnblogs.com/lys-lyy/p/11087395.html

    展开全文
  • Qt的容器类提供了两种类型的迭代器:Java风格迭代器STL风格迭代器。如果只是想按顺序遍历容器中的项目,那么还可以使用Qt的foreach关键字。 、Java风格的迭代器 Java风格的迭代器在Qt4中加入,比STL风格的...
  • 容器

    万次阅读 多人点赞 2019-07-12 09:07:32
    Java 208 道面试题 · 容器 ... java.util.Collection 是个集合接口(集合个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collecti...
  • 并发类容器

    2018-10-13 22:44:09
    、 ConcurrentHashMap 减少锁粒度的思想 http://www.importnew.com/28263.html https://www.cnblogs.com/dolphin0520/p/3932905.html ...、CopyOnWrite 读写分离的思想,读写是不同的容器 ...
  • 简单的来说Java容器类类库的用途是“持有对象”,并将其划分为两个不同的概念:1.Collection:个独立元素的序列,这些元素都服从条或者多条规则。List必须按照插入的顺序保存元素,而set不能有重复的元素。Queue...
  • Delphi中的容器类()

    2012-11-08 06:04:43
    Delphi并没有象C++Java那样将字符串定义为,因此TList本身不能直接存储字符串,而字符串列表又是使用非常广泛的,为此Borland提供了TStrings作为存储字符串的基类,应该说是它除了TList之外另外个最重要的...
  • 容器类和接口框架

    2016-10-09 12:48:34
    、在java.util包下主要的接口: Collection |—List |—Set Map 主要的 ArrayList LinkedListHashSet LinkedHashSet TreeSetHashMap LinkedHashMap TreeMapVector Hashtable Collections、在java...
  • JAVA通幽(容器类

    2018-10-16 21:21:10
    前言  本章将讲解集合的基本概念 基础 1.数组的优劣 ...综上所述,我们需要个更为强大的容器来存储所需要的数据对象集合,基本容器类如下所示:  为了更好的学习容器,这里先行说一下泛型...
  • 并发类容器-第

    2019-06-23 14:39:00
    、ConcurrentHashMap 在1.7 1.8 ,不同版本的实现方式 、基础知识夯实 1.首先是我们需要懂得几点是在java中的集中运算 直接看代码吧 public class IntToBinary { public static void main(String[]...
  • java的容器类一共有两种主要类型,ColllectionMap。 两者的区别是:Collection是单个元素,而Map是存储个键值对 两者的子类关系如下图所示: ,子类介绍 1,Collection的子类如下: List:将以...
  • 官方API文档:Interface List&lt;E&gt;Type Parameters:E - the type of elements in this listAll Superinterfaces:Collection&...List 接口个 List 是个元素有序的、可以重复、可...
  •  在Java中,同步容器包括两个部分,个是vectorHashTable,查看vector、HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字synchornized。...
  • 食品对于人类而言拥有着获取营养品味美食两个功能,而食品器具、容器、...该文档为食品器具、容器、包装检验方法-聚对苯甲酸乙酯塑料之检验,是份很不错的参考资料,具有较高参考价值,感兴趣的可以下载看看
  • Java容器类

    2019-10-23 20:20:11
    /* 1,表格数据储存 JavaBean 可以创建表示个实例,用Javabean封装一下, 在创建这个的对象,个对象代表条数据,个...,所有类型转化都是自动的隐式的,提高代码重用率 不使用泛型 //需要人为的...
  • JAVA 容器类总结

    2017-04-25 16:56:00
    原文出处:... 1.java容器分类图  说明:第个图为简化图(其中粗线部分是重点的容器),第个图为完整容器分类图 ...2.容器类接口抽象容器类   2.1 说明  容器接口是容器的基础
  • package java.util包中的Collection相关接口和类如下图:Collection.png仅讨论Java.util包中的常见集合,不涉及java.util的子包concurrent中的并发集合。可以这样简单的来对待容器中集合:Collection_common.png...
  • 容器中的Frame为个矩形的框架对象,对应QFrame,QFrame是PyQt中带框架部件的所有的基类,如菜单、进度条、Label标签等。QFrame除了派生子外,也可以直接用于创建没有任何内容的简单占位符框架,用于占用...
  • 每个元素按键的取值升序排列(对于个关联容器,使用迭代器s在区间[s.begin(), s.end())内遍历,访问到的序列总是升序) 优势: 可以根据键值高效地查找元素 要求:键值的类型必须能够用 int、double等...
  • JAVA之容器类简介

    2017-11-18 15:47:58
    容器简介 容器个将多个元素组合到个单元的对象,是代表组对象的对象,容器中的对象成为它的元素。...根据容器所包含的对象的不同可以将容器接口分为CollectionMap两大,实现Collecti
  • MFC容器MFC中容器类的使用

    千次阅读 2013-10-17 14:02:12
    对于MFC中所提到的基本容器类,做个基本的介绍编程操作。  如有数据结构基础,可以直接看第四部分对容器类的操作。  第,MFC提供三种基本的容器类:arrays(数组), lists(链表), maps(映射,也称作...
  • 同步容器和并发容器

    2017-05-11 23:56:37
    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列、Synchronizer(比如CountDownLatch)。今天我们就来讨论下同步容器。  以下是本文的目录大纲: ...
  • lesson7-容器类

    2016-02-02 11:47:06
    、容器 容器用于存储给定数据类型的值,它是模板,根据提供T的不同,存储不同的数据 ...Qt的容器有连续容器和关联容器 连续容器:QVector、 QLinkedList、 QList 关联容器:QMap、 QHash 、QVector、...
  • Qt容器类整理

    千次阅读 2015-08-03 17:17:45
    Qt既提供了诸如QVector、QLinkedListQList等的连续容器,也提供...例如,qSort()算法对个连续容器进行排序,qBinaryFind()在经过排序的连续容器上执行进制搜索。 (1)连续容器  QVector是种与数组相似的

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,096
精华内容 1,238
关键字:

一类容器和二类容器