您的位置:首页 > 编程语言 > Java开发

Java并发:原子变量和非阻塞同步机制

2016-04-04 21:59 926 查看

一、互斥锁

互斥锁虽然能够保障内存可见性和原子性,保障共享数据的安全访问,但是作为一种较为粗暴的同步机制,有很多缺点:
1.      悲观锁:每次拿数据都会先加锁,降低代码并发度。
2.      上下文切换开销大:线程的挂起和恢复需要很大的开销,并且存在较长时间的中断
3.      优先级反转:当一个线程在等待一个锁时不能做其他事情,如果持有锁的线程被延迟执行,如发生缺页错误,调度延迟等,那么将导致需要锁的线程无法执行下去,如果被阻塞的线程优先级较高,持有锁的线程优先级较低,将发生优先级反转。
4.      死锁:两个持有锁的线程都需要申请对方的锁,且该锁为不可放弃的内置锁,那么将发生死锁。

二、volatile

更加轻量级的同步机制,因为使用这些变量时不会发生上下文切换或者线程调度。
但是虽提供了内存可见性保证,却无法构建原子的复合操作,如计数器、互斥量等。

三、原子操作

硬件支持
几乎所有现代处理器都包含了某种形式的原子“读-改-写”指令,比较常见的例如:比较并交换CAS(compare-and-swap)、关联加载/条件存储(Load-Linked/Store-conditional),操作系统和JVM都基于这些指令来实现锁和并发的数据结构。
 
JVM支持
java5.0之后引入了JVM底层的支持,在int、long和对象引用等类型上都提供了CAS操作,并且JVM会把它们编译为底层硬件提供的最有效的方法。有了JVM底层支持,原子变量类(例如java.util.concurrent.atomic中的AtomicXXX)中使用了这些底层的JVM支持为数字类型和引用类型提供了高效的CAS操作,而在java.util.concurrent包中大多数类在实际实现时则直接或者间接地使用了这些原子变量类。

3.1 优缺点

优点:
失败不挂起:CAS是一种乐观锁,多个线程尝试使用CAS更新同一个变量时,只有其中一个线程能够更新变量的值,其它线程都将失败,然后失败的线程并不会挂起,这与获取锁的情况不同,获取锁失败的时候线程将被挂起,CAS失败的话将会被告知失败,调用代码可以选择再次尝试或者执行一些恢复操作等。
另外在性能上:一段“获取锁->执行操作->释放锁”的代码,在没有任何竞争的情况下,其运行的开销大约是CAS的两倍,更不用说在并发环境下。
 
缺点
需要调用者来处理竞争问题(通过重试、回退、放弃),而在锁中可以自动处理竞争问题(线程在获得锁之前将会一直阻塞)。

3.2 与互斥锁、volatile的比较

与锁相比较
原子变量比锁的粒度更加细、量级更轻,这对于在多处理器系统上实现高性能的并发代码是非常关键的,原子变量将发生竞争的范围缩小到单个变量上,这是你获得的粒度最细的情况,多个线程之间不会因为竞争而发生上下文切换的情况。
 
与volatile比较
原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的“读– 改– 写”操作。比如AtomicInteger表示一个int类型的值,它提供了get和set方法与volatile类型的int变量在读取和写入上有着相同的内存语义,它还提供了原子的compareAndSet方法、以及原子的添加、递增和递减方法。

3.3 非阻塞的同步机制

非阻塞算法:就是用底层的原子机器指令,如比较并交换(CAS)代替锁来确保数据在并发访问中的一致性。与基于锁的方案相比,非阻塞算法在设计和实现上都要更加复杂,但是它们在可伸缩性和活跃性上都有很大的优势。
使用原子变量类来构建非阻塞算法是最常用的手段!!!
 
下面是一个使用CAS实现非阻塞计数器的例子:public classCASCounter {
private SimulatedCAS value;
public int getValue(){return value.get();}
public int increment(){
int v;
do
{
v= value.get();
//失败重试
}while(v != value.conpareAndSet(v, v+1));
return v + 1;
}
}Java中原子操作的使用举例:
在java.util.concurrent.atomic包中的原子变量类AtomicInteger/AtomicLong的递增方法以及AtomicRefernce类的conpareAndSet方法都调用了CAS操作:
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
 
java.util.concurrent包中很多类也都使用到了原子变量类或者CAS操作。
如信号量Semaphore的acquire()方法也用到了int类型的CAS操作:return unsafe.compareAndSwapInt(node,waitStatusOffset,expect,update); 再比如ConcurrentLinkedQueue的入队函数offer()也用到了CAS操作:return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); ConcurrentSkipListMap/ConcurrentSkipListSet中的put方法也用到了原子操作:return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val); 有的并发容器类没有用到原子操作,比如:ArrayBlockingQueue,内部使用ReentrantLock+Condition来实现并发的安全访问以及阻塞等功能。CopyOnWriteArrayList/CopyOnWriteArraySet,内部同样使用ReentrantLock实现并发安全访问。注意也有的并发容器即用到了互斥锁也用到了原子操作,比如LinkedBlockingQueue
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: