Java线程与并发编程实践----额外的并发工具类
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程与并发编程实践----额外的并发工具类相关的知识,希望对你有一定的参考价值。
一、并发集合
java.util包下提供了很多的集合类,如ArrayList、TreeSet、HashMap,但是这些
集合都是非线程安全的,并且对于单列集合的迭代器,采用的是快速失败机制,当正在迭代
遍历的集合被其它线程修改时,便会抛出 java.util.ConcurrentModificationException。
这显然对于多线程操作的集合是十分不方便的,但早Colections这个工具类中有方法可以返回
线程安全的集合,然而这种集合对于高并发环境,性能十分低下。
于是,java.util.concurrent包下提供了很多相关集合的类,即并发集合,这些集合
具有并发性能和高扩展性,并且返回的是弱一致性的迭代器,即不再是快速失败机制的迭代器。
大致描述如上,具体翻查jdk。下面单独研究使用并发集合完成生产者消费者的问题。
使用BlockingQueue和ArrayBlockingQueue实现生产者消费者:
ArrayBlockingQueue是一个由数组支的有界阻塞队列,当队列满载时,put()方法会阻塞,当
队列为空时,take()方法阻塞,因此用这种队列可以很轻松的实现消费者、生产者。
转到http://blog.51cto.com/12222886/1963884
深入学习ConcurrentHashMap:
重点了解此方法。
ConcurrentHashMap是一个线程安全的集合,但是在多线程德操作之下它并不是线程安全
的,例如如下代码:
if(!map.containsKey("something")) map.put("something", "something");
试想,当我们再判断完之后,还未put之前,有一条线程向map中put进一个键为something的值,
然后我们再继续put,那么刚才的那个值是不是就被覆盖了?这就产生了安全问题。。为了保证
安全我们应该这样做:
synchronized (map) { if (!map.containsKey("something")) map.put("something", "something"); }
以上可以解决安全问题,但是性能会被降低。。我们应该使用ConcurrentHashMap所提供的
一个API,putIfabsent(K key,V value),它的含义是,假如键值不存在,便put进去,它是线程
安全的,并且性能更高,相当于如下代码:
synchronized (map) { if (!map.containsKey("something")) return map.put("something", "something"); else return map.get(key); }
二、原子变量
和对象监听器关联的那些内置锁一直以来都有性能不佳的问题,后来出现了
很多非阻塞算法,可大大提高性能和扩展性。
java.util.concurrent.atomic提供了高效非阻塞算法。它支持单个变量可进行无
锁及线程安全的操作。有如下原子类:
原子变量用于实现计数器、序列生成器以及其它构造。在线程高争用的环境下,这些构造要求互斥
而不影响性能。假如有如下代码:
package xiancheng; public class ID { private static volatile long nextID = 1; static synchronized long getNextID() { return nextID++; } }
上述代码中volatile保证了可见性,synchronized保证了互斥性,在多线程环境下,上述代码没有
问题,然而在高争用的环境中性能会很低。我们可以用原子变量代替上述代码:
class ID2{ private static AtomicLong nextID = new AtomicLong(); public static long getNextID() { return nextID.getAndIncrement(); } }
上述代码完全保证了可见性,互斥性以及操作的原子性,并且由于它实现了高争用下的非阻塞算法,
因此它的性能相对来说高了很多。
那么问题来了,为什么原子变量能够提高性能????
Compare-and-swap(CAS机制)
Compare-and-swap(CAS机制)是一个针对非抢占式微处理器的一条指定指令的宽泛术语,这条
指令读取内存的位置,比较读到的值和期望的值,当读到值和期望值匹配时,就将新值存储到该内存
位置,否则,什么也不发生。
CAS支持原子的读-改-写序列,通常这样使用:
(1)从地址A读取x
(2)在x进行多步计算
(3)使用CAS将A值从x变为y。在进行这些操作时,如果A值没有改变,CAS就成功了
那CAS到底优越之处在哪呢?
package xiancheng; public class ID { private static volatile long nextID = 1; static synchronized long getNextID() { return nextID++; } }
上面的代码使用synchronized,高争用环境下的监听锁会导致过多的上下文切换,这样会阻碍所有线程
并且导致应用程序无法很好的扩展。而CAS机制不适用监听器来是操作原子化,而是在修改ID的值之前会
进行判断,如果该值没发生过变化,就将新值赋值给该变量,如果发生变化了就什么也不做,而在这中间
对值是否发生过变化的判断是利用CAS指令完成的。
java.util.concurrent.locks.ReentrantLock就使用了CAS机制改善了性能,原子类也利用了CAS机制。
以上是关于Java线程与并发编程实践----额外的并发工具类的主要内容,如果未能解决你的问题,请参考以下文章