java

位置:IT落伍者 >> java >> 浏览文章

Java多线程之Atomic:原子变量与原子类


发布日期:2020年04月07日
 
Java多线程之Atomic:原子变量与原子类

何谓Atomic?

Atomic一词跟原子有点关系后者曾被人认为是最小物质的单位计算机中的Atomic是指不能分割成若干部分的意思如果一段代码被认为是Atomic则表示这段代码在执行过程中是不能被中断的通常来说原子指令由硬件提供供软件来实现原子方法(某个线程进入该方法后就不会被中断直到其执行完成)

在x 平台上CPU提供了在指令执行期间对总线加锁的手段CPU芯片上有一条引线#HLOCK pin如果汇编语言的程序中在一条指令前面加上前缀LOCK经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低持续到这条指令结束时放开从而把总线锁住这样同一总线上别的CPU就暂时不能通过总线访问内存了保证了这条指令在多处理器环境中的原子性

ncurrent中的原子变量

无论是直接的还是间接的几乎 ncurrent 包中的所有类都使用原子变量而不使用同步类似 ConcurrentLinkedQueue 的类也使用原子变量直接实现无等待算法而类似 ConcurrentHashMap 的类使用 ReentrantLock 在需要时进行锁定然后 ReentrantLock 使用原子变量来维护等待锁定的线程队列

如果没有 JDK 中的 JVM 改进将无法构造这些类这些改进暴露了(向类库而不是用户类)接口来访问硬件级的同步原语然后ncurrent 中的原子变量类和其他类向用户类公开这些功能

ncurrentatomic的原子类

这个包里面提供了一组原子类其基本的特性就是在多线程环境下当有多个线程同时执行这些类的实例包含的方法时具有排他性即当某个线程进入方法执行其中的指令时不会被其他线程打断而别的线程就像自旋锁一样一直等到该方法执行完成才由JVM从等待队列中选择一个另一个线程进入这只是一种逻辑上的理解实际上是借助硬件的相关指令来实现的不会阻塞线程(或者说只是在硬件级别上阻塞了)其中的类可以分成

AtomicBooleanAtomicIntegerAtomicLongAtomicReference

AtomicIntegerArrayAtomicLongArray

AtomicLongFieldUpdaterAtomicIntegerFieldUpdaterAtomicReferenceFieldUpdater

AtomicMarkableReferenceAtomicStampedReferenceAtomicReferenceArray

其中AtomicBooleanAtomicIntegerAtomicLongAtomicReference是类似的

首先AtomicBooleanAtomicIntegerAtomicLongAtomicReference内部api是类似的举个AtomicReference的例子

使用AtomicReference创建线程安全的堆栈

Java代码

public class LinkedStack<T> {

private AtomicReference<Node<T》 stacks = new AtomicReference<Node<T》()

public T push(T e) {

Node<T> oldNode newNode;

while (true) { //这里的处理非常的特别也是必须如此的

oldNode = stacksget()

newNode = new Node<T>(e oldNode)

if (pareAndSet(oldNode newNode)) {

return e;

}

}

}

public T pop() {

Node<T> oldNode newNode;

while (true) {

oldNode = stacksget()

newNode = oldNodenext;

if (pareAndSet(oldNode newNode)) {

return oldNodeobject;

}

}

}

private static final class Node<T> {

private T object;

private Node<T> next;

private Node(T object Node<T> next) {

thisobject = object;

thisnext = next;

}

}

}

然后关注字段的原子更新

AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<TV>是基于反射的原子更新字段的值

相应的API也是非常简

单的但是也是有一些约束的

)字段必须是volatile类型的!volatile到底是个什么东西请查看

)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致也就是说调用者能够直接操作对象字段那么就可以反射进行原子操作但是对于父类的字段子类是不能直接操作的尽管子类可以访问父类的字段

)只能是实例变量不能是类变量也就是说不能加static关键字

)只能是可修改变量不能使final变量因为final的语义就是不可修改实际上final的语义和volatile是有沖突的这两个关键字不能同时存在

)对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段不能修改其包装类型(Integer/Long)如果要修改包装类型就需要使用AtomicReferenceFieldUpdater

在下面的例子中描述了操作的方法

[java]

import ncurrentatomicAtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterDemo {

class DemoData{

public volatile int value = ;

volatile int value = ;

protected volatile int value = ;

private volatile int value = ;

}

AtomicIntegerFieldUpdater<DemoData> getUpdater(String fieldName) {

return AtomicIntegerFieldUpdaternewUpdater(DemoDataclass fieldName)

}

void doit() {

DemoData data = new DemoData()

Systemoutprintln( ==> +getUpdater(valuegetAndSet(data ))

Systemoutprintln( ==> +getUpdater(valueincrementAndGet(data))

Systemoutprintln( ==> +getUpdater(valuedecrementAndGet(data))

Systemoutprintln(true ==> +getUpdater(valuecompareAndSet(data ))

}

public static void main(String[] args) {

AtomicIntegerFieldUpdaterDemo demo = new AtomicIntegerFieldUpdaterDemo()

demodoit()

}

}

在上面的例子中DemoData的字段value/value对于AtomicIntegerFieldUpdaterDemo类是不可见的因此通过反射是不能直接修改其值的

AtomicMarkableReference类描述的一个<ObjectBoolean>的对可以原子的修改Object或者Boolean的值这种数据结构在一些缓存或者状态描述中比较有用这种结构在单个或者同时修改Object/Boolean的时候能够有效的提高吞吐量

AtomicStampedReference类维护带有整数标志的对象引用可以用原子方式对其进行更新对比AtomicMarkableReference类的<ObjectBoolean>AtomicStampedReference维护的是一种类似<Objectint>的数据结构其实就是对对象(引用)的一个并发计数但是与AtomicInteger不同的是此数据结构可以携带一个对象引用(Object)并且能够对此对象和计数同时进行原子操作

在本文结尾会提到ABA问题而AtomicMarkableReference/AtomicStampedReference在解决ABA问题上很有用

Atomic类的作用

使得让对单一数据的操作实现了原子化

使用Atomic类构建复杂的无需阻塞的代码

访问对个或个以上的atomic变量(或者对单个atomic变量进行次或次以上的操作)通常认为是需要同步的以达到让这些操作能被作为一个原子单元

无锁定且无等待算法

基于 CAS (compare and swap)的并发算法称为 无锁定算法因为线程不必再等待锁定(有时称为互斥或关键部分这取决于线程平台的术语)无论 CAS 操作成功还是失败在任何一种情况中它都在可预知的时间内完成如果 CAS 失败调用者可以重试 CAS 操作或采取其他适合的操作

如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作就可以说该算法是 无等待的与此形成对比的是 无锁定算法要求仅 某个线程总是执行操作(无等待的另一种定义是保证每个线程在其有限的步骤中正确计算自己的操作而不管其他线程的操作计时交叉或速度这一限制可以是系统中线程数的函数例如如果有 个线程每个线程都执行一次CasCounterincrement() 操作最坏的情况下每个线程将必须重试最多九次才能完成增加

再过去的 年里人们已经对无等待且无锁定算法(也称为 无阻塞算法)进行了大量研究许多人通用数据结构已经发现了无阻塞算法无阻塞算法被广泛用于操作系统和 JVM 级别进行诸如线程和进程调度等任务虽然它们的实现比较复杂但相对于基于锁定的备选算法它们有许多优点可以避免优先级倒置和死锁等危险竞争比较便宜协调发生在更细的粒度级别允许更高程度的并行机制等等

常见的

非阻塞的计数器Counter

非阻塞堆栈ConcurrentStack

非阻塞的链表ConcurrentLinkedQueue

               

上一篇:Java多线程初学者指南(7):向线程传递数据的三种方法

下一篇:用Java设计防病毒的电子邮件程序