博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 并发:第六部分 - 原子变量
阅读量:6894 次
发布时间:2019-06-27

本文共 4135 字,大约阅读时间需要 13 分钟。

hot3.png

当几個线程都不能访问某個数据(通常是某個变量)时,你必须同步数据的访问以确保变量的可见性和正确性。举個例子,如果你有如下壹個简单的计数器(是的,我们又壹次举了这個例子):
public class Counter {    private int value;    public int getValue(){        return value;    }    public int getNextValue(){        return value++;    }    public int getPreviousValue(){        return value--;    }}
这個类在单线程环境下运行很正常,但是当多個线程同时访问相同的 Counter 实例时运行却完全不正常。如果你不知道为什么,阅读下 。你可以在方法层面上解决同步的问题:
public class SynchronizedCounter {    private int value;    public synchronized int getValue(){        return value;    }    public synchronized int getNextValue(){        return value++;    }    public synchronized int getPreviousValue(){        return value--;    }}
这個类现在工作正常。但是锁不是轻量级方法,而且有几個坏处。当几個线程尝试获取相同的锁的时候,壹個或者多個线程会被挂起,然后他们会在之后被激活。当关键的部分很少时,这种开销真的很重,特别是当锁经常被获取时,关于这壹点其实有很多争议。另外壹個坏处就是,如果已经获得锁的线程延时了( 由于缺页或结束时间量引起的),等待锁的其它线程在等待的过程中除了等待以外什么都做不了,其它的线程不能轮流执行。
那么如何避免这种不利因素呢?我们必须使用非阻塞算法。这种算法不会使用阻塞机制,这壹事实更可扩展和执行。这些算法使用底层的机器指令,它们都是原子的,以确保更高级别的操作也是原子的。虽然锁是壹种悲观的做法,我们仍然可以使用悲观的方法开发算法。这壹次, 我们将检测线程之间的碰撞,如果在这种情况下操作失败,我们就做别的事情(通常来说都是尝试同样的操作)。
实际的处理器提供了最常用的操作大大简化实施这些非阻塞算法的一些指令,今天使用最多的是比较和交换操作(简称 CAS)。这個操作需要三個参数,内存地址,期待的当前值和新的值。如果当前值是期待的结果,它会自动更新在给定内存地址上的值,否则它什么都不做。在这两种情况下,操作执行之后,它会返回在这個内存地址上的结果。所以当多個线程尝试执行 CAS 操作时,壹個线程获胜了,则其它线程什么都不做。所以调用者可以选择再次尝试该操作或者什么都不做。我们经常使用这個操作来实现其它的操作,比较和交换。这個方法做了和 CAS 壹样的事情,但是返回了壹個布尔型值标识该操作是成功还是失败。

在 Java 5.0 之前,这個操作还没有直接开放给开发人员来使用,但是在 Java 5.0 中添加了壹些原子变量(类似于 int, long,boolean 和引用值)。其中 int 和 long 的版本也支持数字操作。Java 虚拟机使用由硬件机器提供的更好的操作编译这些类,比如说 CAS 或者使用锁的 Java 实现。这里有相关的几個类:

java.util.concurrent.atomic.AtomicIntegerjava.util.concurrent.atomic.AtomicLongjava.util.concurrent.atomic.AtomicBooleanjava.util.concurrent.atomic.AtomicReference
java.util.concurrent.atomic.AtomicIntegerArrayjava.util.concurrent.atomic.AtomicLongArrayjava.util.concurrent.atomic.AtomicIntegerFieldUpdater
java.util.concurrent.atomic.AtomicLongFieldUpdater

所有这些类都能通过 compareAndSet() 方法和其它操作(如get(),set() 和 getAndSet() 方法)支持比较和交换操作。其中 setter 操作是用 compareAndSet() 方法实现的。这些类支持多线程访问,并且比同步所有操作拥有更好的扩展性。接下来是我们使用 类重写的我们的计数器:

public class AtomicCounter {    private final AtomicInteger value = new AtomicInteger(0);    public int getValue(){        return value.get();    }    public int getNextValue(){        return value.incrementAndGet();    }    public int getPreviousValue(){        return value.decrementAndGet();    }}
其中 incrementAndGet() 和 decrementAndGet() 方法是由 类和 类提供的数值的操作。你同样可以使用 getAndDecrement(),getAndIncrement(),getAndAdd(int i) 和 addAndGet() 方法。这個版本的计数器比同步的那個版本运行速度更快,而且它也是线程安全的。
如果你只想使用 compareAndSet() 方法,这里有壹個我们使用 compareAndSet() 方法实现 increment() 的例子:
public void increment(AtomicInteger integer){    while(true){        int current = integer.get();        int next = current + 1;        if(integer.compareAndSet(current, next)){            return;        }    }}
上面的代码看起来似乎有点复杂,但是这是使用非阻塞算法需要付出的成本。当我们检测到了碰撞,我们会重试直到这個操作成功。这是非阻塞算法最普遍的模式。
如下是壹個使用 实现的线程安全的栈:
public class Stack {    private final AtomicReference
head = new AtomicReference
(null); public void push(String value){ Element newElement = new Element(value); while(true){ Element oldHead = head.get(); newElement.next = oldHead; //尝试在栈的头部设置壹個新的元素 if(head.compareAndSet(oldHead, newElement)){ return; } } } public String pop(){ while(true){ Element oldHead = head.get(); //栈是空的,直接返回 null if(oldHead == null){ return null; }            //栈不是空的,获取下壹個元素 Element newHead = oldHead.next; //尝试在头部设置新的元素 if(head.compareAndSet(oldHead, newHead)){ return oldHead.value; } } } private static final class Element { private final String value; private Element next; private Element(String value) { this.value = value; } }}
上述例子比同步两個方法更加复杂了,但是如果有资源的争夺(那怕没有资源的争夺),它会提供更好的性能。
到目前为止这篇文章结尾了。总而言之,原子变量类真的是壹种实现非阻塞算法的好方法,而且原子变量类也是 volatile 变量很好的替代品,因为它们也可以提供原子性和可见性。

本文英文原文出自,中文翻译首发开源中国社区 ,转载请注明原始出处。

转载于:https://my.oschina.net/bairrfhoinn/blog/167071

你可能感兴趣的文章
root登陆欢迎界面设置
查看>>
#单元测试#以karma+mocha+chai 为测试框架的Vue webpack项目(一)
查看>>
#和妹妹一起做毕业设计#从需求到软件发布的流程记录—— 搭建前端项目篇
查看>>
<mvc:annotation-driven />与<context:annotation-config />
查看>>
C# Winfrom 进程&多线程
查看>>
axios请求、返回拦截器
查看>>
谷歌三大核心技术(二)Google MapReduce中文版
查看>>
WinSock 异步I/O模型 转载
查看>>
RxJava 机制
查看>>
js 运算符
查看>>
PHP Cookie Session
查看>>
Linux中如何让进程(或正在运行的程序)到后台运行?
查看>>
https://sweetalert2.github.io/
查看>>
V-rep学习笔记:Reflexxes Motion Library 2
查看>>
利用Navicat实现MySQL数据库结构对比和同步
查看>>
protobuf c++例子
查看>>
《TCP/IP详解1》笔记(第1章 概述)
查看>>
Dubbo项目实战 (二) 注册中心zookeeper-3.4.6集群以及高可用
查看>>
[树形DP][概率期望]JZOJ 4225 宝藏
查看>>
Appium-desktop安装与使用
查看>>