并发集合
数据结构是编程中的基本元素,几乎每一个程序都使用一种或多种数据结构来存储和管理数据。java api提供了包含接口、类和算法的java集合框架,它实现了可用在程序中的大量数据结构。
当须要在并发程序中使用数据集合时,必需要谨慎地选择相应的实现方式。大多数集合类不能直接用于并发应用,由于它们没有对自己数据的并发访问进行控制。
若是一些并发任务共享了一个不适用于并发任务的数据结构,将会遇到数据不一致的错误,并将影响程序的准确运行。这类数据结构的一个例子是ArrayList类。
java提供了一些能够用于并发程序中的数据集合,它们不会引发任何问题。通常来讲,java提供了两类适用于并发应用的集合:
1.阻塞式集合(Blocking Collection):这类集合包括添加和移除数据的方法。当集合已满或者为空时,被调用的添加或移除方法就不能当即被执行,那么调用这个方法的线程将被阻塞,一直到该方法能够被成功执行。
2.非阻塞式集合(Non-Blocking Collection):这类集合也包括添加和移除数据的方法。若是方法不能当即被执行,则返回null或者抛出异常,可是调用这个方法的线程不会被阻塞。
并发应用中经常使用的java集合类:
1.非阻塞式列表对应的实现类:ConcurrentLinkedDeque类
2.阻塞式列表对应的实现类:LinkedBlockingDeque类
3.用于数据生成或消费的阻塞式列表对应的实现类:LinkedTransferQueue类
4.按优先级排序列表元素的阻塞式列表对应的实现类:PriorityBlockingQueue类
5.带有延迟列表元素的阻塞式列表对应的实现类:DelayQueue类
6.非阻塞式可遍历映射对应的实现类:ConcurrentSkipListMap类
7.随机数字对应的实现类:ThreadLocalRandom类
8.原子变量对应的实现类:AtomicLong和AtomicIntegerArray类
1、ConcurrentLinkedDeque类提供的经常使用方法:
1.getFirst()和getLast():分别返回列表中的第一个和最后一个元素,返回的元素不会从列表中移除。若是列表为空,这两个方法抛出NoSuchElementExcpetion异常。
2.peek()、peekFirst()和peekLast():分别返回列表中第一个和最后一个元素,返回的元素不会从列表中移除。若是列表为空,这些方法返回null。
3.remove(), removeFirst(), removeLast():这些方法返回列表的第一个和最后一个元素。他们从列表中移除返回的元素。若是列表是空的,这些方法抛出一个 NoSuchElementException例外。
4.pollFirst()和pollLast():pollFirst()方法返回和删除列表的第一个元素和pollLast()方法返回和删除最后一个元素的列表。若是列表为空,这些方法返回一个null值。
5.size():该方法返回的值可能不是真实的,尤为当有线程在添数据或者移除数据时。这个方法须要遍历整个列表来计算元素数量,而遍历过的数据可能已经改变。仅当没有任何线程修改列表时,才能保证返回的结果是准确的。
2、LinkedBlockingDeque类提供的经常使用方法:
1.takeFirst和takeLast():分别返回列表中第一个和最后一个元素,返回的元素会从列表中移除。若是列表为空,调用方法的线程将被阻塞直到列表中有可用的元素出现。
2.getFirst()和getLast():分别返回列表中第一个和最后一个元素,返回的元素不会从列表中移除。若是列表为空,则抛出NoSuchElementException异常。
3.peek()、peekFirst()和peekLast():分别返回列表中第一个和最后一个元素,返回的元素不会从列表中移除。若是列表为空,返回null。
4.poll()、pollFirst()和pollLast():分别返回列表中第一个和最后一个元素,返回的元素将会从列表中移除。若是列表为空,返回null。
5.add()、addFirst()和addLast():分别将元素 添加到列表中第一位和最后一位。若是列表已经满了,这些方法将抛出ILLegalStateException异常。
3、PriorityBlockingQueue类
数据结构中的一个经典需求是实现一个有序列表。java引用了PriorityBlockingQueue类来知足这类需求。
全部添加进PriorityBlockingQueue的元素必须实现Comparable接口。这个接口提供了compareTo()方法,它的传入参数是一个同类型的对象。这样就有了两个类型的对象而且相互比较:其中一个是执行这个方法的对象,另外一个是参数传入的对象。这个方法必须返回一个数字值,若是当前对象小于参数传入的对象,那么返回一个小于0的值;若是当前对象大于参数传入的对象,那么返回一个大于0的值;若是两个对象相等就是返回0.
当插入元素时,PriorityBlockingQueue使用compareTo()方法来决定插入元素的位置。元素越大越靠后。
PriorityBlockingQueue的另外一个重要的特性是:它是阻塞式数据结构。当它的方法被调用而且不能当即执行时,调用这个方法的线程将被阻塞直到方法执行成功。
PriorityBlockingQueue类提供的经常使用方法:
1.clear():移除队列中的全部元素
2.take():返回队列中的第一个元素并将其移除。若是队列为空,线程阻塞直到队列中有可用的元素。
3.put(E e):E是PriorityBlockingQueue的泛型参数,表示传入参数的类型。这个方法把参数对应的元素插入到队列中。
4.peek():返回队列中的第一个元素,但不将其移除。
4、DelayQueue类
java api提供了一种用于并发应用的有趣的数据结构,即DelayQueue类。这个类能够存放带有激活日期的元素。当调用方法从队列中返回或提取元素时,将来的元素日期将被忽略。这些元素对于这些方法是不可见的。
为了具备条用行为,存放到DelayQueue类中的元素必须继承Delayed接口。delayed接口使对象成为延迟对象,它使存放在DelayQueue类中的对象具备了激活日期,即到激活日期的时间。该接口强制执行下列两个方法:
1.compareTo(Delayed o):Delayed接口继承了Comparable接口,所以有了这个方法。若是当前对象的延迟值小于参数对象的值,将返回一个小于0的值;若是当前对象的延迟值大于参数对象的值,将返回一个大于0的值;若是二者的延迟值相等则返回0.
2.getDelay(TimeUnit unit):这个方法返回到激活日期的剩余时间,单位由单位参数指定。
DelayQueue类提供的经常使用方法:
1.clear():移除队列中的全部元素。
2.offer(E e):E是DelayQueue的泛型参数,表示传入参数的类型。这个方法把参数对应的元素插入到队列中。
3.peek():返回队列中的第一个元素,但不将其移除。
4.take():返回队列中的第一个元素,并将其移除。若是队列为空,线程将被阻塞直到队列中有可用的元素。
5、ConcurrentSkipListMap类
java api提供了一种用于并发应用程序中的有趣数据结构,即ConcurrentNavigableMap接口及其实现类。实现这个接口的类以以下两部分存放元素:
1.一个键值(Key),它是元素的标识而且是惟一的。
2.元素其余部分数据。
每个组成部分必须在不一样的类中实现。
java api也提供了一个实现ConcurrentSkipListMap接口的类,ConcurrentSkipListMap接口实现了与ConcurrentNavigableMap接口有相同行为的一个非阻塞式列表。从内部实现机制来说,它使用了一个Skip List来存放数据。Skip List是基于并发列表的数据结构,效率与二叉树相近。
当插入元素到映射中时,ConcurrentSkipListMap接口类使用键值来排序全部元素。除了提供返回一个具体元素的方法外,这个类也提供获取子映射的方法。
ConcurrentSkipListMap类提供的经常使用方法:
1.headMap(K toKey):K是在ConcurrentSkipListMap对象的 泛型参数里用到的键。这个方法返回映射中全部键值小于参数值toKey的子映射。
2.tailMap(K fromKey):K是在ConcurrentSkipListMap对象的 泛型参数里用到的键。这个方法返回映射中全部键值大于参数值fromKey的子映射。
3.putIfAbsent(K key,V value):若是映射中不存在键key,那么就将key和value保存到映射中。
4.pollLastEntry():返回并移除映射中的最后一个Map.Entry对象。
5.replace(K key,V value):若是映射中已经存在键key,则用参数中的value替换现有的值。
6、ThreadLocalRandom类
java api提供了一个特殊类用以在并发程序中生成伪随机数,即ThreadLocalRandom类。它是线程本地变量。每一个生成随机数的线程都有一个不一样的生成器,可是都在同一个类中被管理,对程序员来说是透明的。
相比于使用共享的Random对象为全部线程生成随机数,这种机制具备更好的性能。
7、AtomicLong类
原子变量(Atomic Variable)是从java5开始引入的,它提供了单个变量上的原子操做。在编译程序时,java代码中的每一个变量、每一个操做都将被转换成机器能够理解的指令。
例如,当给一个变量赋值时,在java代码中只使用一个指令,可是编译这个程序时,指令被转换成JVM语言中的不一样指令。当多个线程共享同一个变量时,就会发生数据不一致的错误。
为了不这类错误,java引入了原子变量。当一个线程在对原子变量操做时,若是其余线程也试图对同一原子变量执行操做,原子变量的实现类提供了一套机制来检查操做是否在一步内完成。通常来讲,这个操做先获取变量值,而后在本地改变变量的值,而后试图用这个改变的值去替换以前的值。若是以前的值没有被其余线程改变,就能够执行这个替换操做。不然,方法将再次执行这个操做。这种操做称为CAS原子操做。
原子变量不使用锁或其余同步机制来保护对其值的并发访问。全部操做都是基于CAS原子操做的。它保证了多线程在同一时间操做一个原子变量而不会产生数据不一致的错误,而且它的性能优于使用同步机制保护的普通变量。
8、AtomicIntegerArray类
当实现一个并发应用时,将不可避免地会有多线程共享一个或多个对象的现象,为了不数据不一致错误,须要使用同步机制来保护对这些共享属性的访问。可是这些同步机制存在下列问题。
1.死锁:一个线程被阻塞,而且试图得到的锁正被其余线程使用,但其余线程永远不会释放这个锁。这种状况使得应用不会继续执行,而且永远不会结束。
2.即便只有一个线程访问共享对象,它仍然须要执行必须的代码来获取和释放锁。
针对这种状况,为了提供更优的性能,java因而引入了比较和交换操做。这个操做使用一下三步修改变量的值:
1.取得变量值,即变量的旧值。
2.在一个临时变量中修改变量值,即变量的新值。
3.若是上面得到的变量旧值与当前变量值相等,就用新值替换旧值。若是已有其余线程修改了这个变量的值,上面得到的变量的旧值就可能与当前变量值不一样。
采用比较和交换机制不须要使用同步机制,不只能够避免死锁而且性能更好。
java在原子变量中实现了这种机制。这些变量提供了实现比较和交换操做的compareAndSet()方法,其余方法也基于它展开。
java也引入了原子数组(Atomic Array)提供对integer或long数字数组的原子操做。java