Java多线程复习与巩固(七)--原子性操作
来源:互联网 发布:git ssh eclipse 端口 编辑:程序博客网 时间:2024/05/29 09:08
前面讲线程同步时,我们对多线程出现的问题进行了分析,在那个例子中,问题的根源在于c++
和c--
这两个操作在底层处理的时候被分成了若干步执行。当时我们用的是synchronized
关键字来解决这个问题,而从synchronize的实现原理中我们知道synchronized
通过monitor
监视器来实现线程同步,这种同步方式要求线程等待monitor
的拥有者线程释放后,才可能进一步执行。这片文章中我们使用另一种方式来解决前面提出的多线程问题。
使用原子操作来解决多线程的问题
先贴出代码:
import java.util.concurrent.atomic.AtomicInteger;public class ThreadCommunicate { static class Counter { private AtomicInteger c = new AtomicInteger(0); public void increment() { c.getAndIncrement(); } public void decrement() { c.getAndDecrement(); } public int value() { return c.get(); } } static class IncrementTask implements Runnable { public void run() { for (int i = 0; i < 10000; i++) { counter.increment(); } } } static class DecrementTask implements Runnable { public void run() { for (int i = 0; i < 10000; i++) { counter.decrement(); } } } private static Counter counter = new Counter(); public static void main(String[] args) throws InterruptedException { Thread i = new Thread(new IncrementTask()); Thread d = new Thread(new DecrementTask()); i.start(); d.start(); i.join(); d.join(); System.out.println(counter.value()); }}
程序运行结果:
0
上面代码中使用了java.util.concurrent.atomic
包中的一个类AtomicInteger
,使用的是类中的getAndIncrement
和getAndDecrement
方法,这两个方法类似于之前例子中的c++
,c--
操作。
AtomicInteger
是对int
类型的封装,AtomicInteger
类中的这两个方法能保证对内存中的int
值的操作都是原子性的,换句话说就能保证一个线程在对int
操作的过程中不会被另一个线程打断,从而使得两个线程不会发生前面文章中出现的指令交叉执行的现象。
对于单处理机CPU来说,原子操作指的是一个不会被“线程调度机制”打断的操作,这种操作一旦开始,就一直占用CPU直到操作结束,中间不会有任何上下文切换(context switch,切换到另外的进程或线程)。
对于多处理机CPU来说,原子操作不仅仅具有前面的那些性质,还应包括“在一个处理机上的操作不会受其他处理机的影响”这一特性,比如说一个处理机修改内存的时候另一个处理机不能修改内存。
java.util.concurrent.atomic
包
像AtomicInteger
这样的类还有很多,它们都在java.util.concurrent.atomic
包中,这些类都是无锁的、线程安全的。
从功能上来说,上面这些类主要分为以下几种:
1. AtomicBoolean
、AtomicInteger
、AtomicLong
、AtomicReference
是对volatile
修饰的单一值进行封装。
volatile
关键字只能保证多线程读取、写入操作的一致性,但不能保证多线程修改操作的原子性,比如++
,--
之类的操作是不能保证线程安全的。但AtomicInteger
和AtomicLong
类保证了++
,--
这类操作的原子性,因此这些方法是线程安全的。特别地,
AtomicBoolean
底层使用int
存储,用1
表示true
,用0
表示false
,因为在Java中boolean
类型的字节长度是不确定的,单个的boolean
编译时会被映射为int
类型,boolean
数组编译时才会被映射为byte
类型的数组。用1
表示true
,用0
表示false
。这里没有提供
byte
、short
、float
、double
、char
的包装类,Java官方文档给出的建议是使用已有的AtomicInteger
和AtomicLong
来自己实现相应的包装类。比如:
- 用
AtomicInteger
来存储byte
数据,进行相应的强制转换即可;- 用
AtomicInteger
来存储float
数据,并使用Float.floatToRawIntBits(float)
和Float.intBitsToFloat(int)
方法进行转换;- 用
AtomicLong
来存储float
数据,并使用Double.doubleToRawLongBits(double)
和Double.longBitsToDouble(long)
进行转换。
Demo:
import java.util.concurrent.atomic.AtomicInteger;public class AtomicFloat extends Number { private int float2Int(float value) { return Float.floatToRawIntBits(value); } private float int2Float(int value) { return Float.intBitsToFloat(value); } private AtomicInteger bits; public AtomicFloat() { this(0f); } public AtomicFloat(float initialValue) { bits = new AtomicInteger(float2Int(initialValue)); } public final boolean compareAndSet(float expect, float update) { return bits.compareAndSet(float2Int(expect), float2Int(update)); } public final void set(float newValue) { bits.set(float2Int(newValue)); } public final float get() { return int2Float(bits.get()); } public float floatValue() { return get(); } public final float getAndSet(float newValue) { return int2Float(bits.getAndSet(float2Int(newValue))); } public final boolean weakCompareAndSet(float expect, float update) { return bits.weakCompareAndSet(float2Int(expect), float2Int(update)); } public double doubleValue() { return (double) floatValue(); } public int intValue() { return (int) get(); } public long longValue() { return (long) get(); }}
2. AtomicIntegerArray
、AtomicLongArray
、AtomicReferenceArray
是对数组类型的值进行原子性操作的封装。
这三个类通过在方法中传入索引作为参数来访问数组中的元素,
AtomicIntegerArray
使用int[]
存储,AtomicLongArray
使用long[]
存储,AtomicReferenceArray
使用T []
泛型数组存储。
3. AtomicIntegerFieldUpdater
、AtomicLongFieldUpdater
、AtomicReferenceFieldUpdater
是对类对象的某个字段进行原子操作。
这三个类都是抽象类,但是它们都提供了一个工厂方法
newUpdater(Class<U> tclass, String fieldName)
来创建实例。
4. AtomicMarkableReference
、AtomicStampedReference
是对AtomicReference
类的扩展。这两个类的区别在于AtomicMarkableReference
使用boolean
与引用类型进行关联,你可以使用这个boolean
标识引用数据是否被删除;而AtomicStampedReference
使用integer
于引用类型进行关联,你可以使用这个integer
代表引用数据更新的版本数值。
下一篇文章讲CAS操作的ABA问题时会提到这两个类的用处
下面的代码是JDK1.8中AtomicMarkableReference
、AtomicStampedReference
的部分代码(1.8之前实现有所不同):
public class AtomicMarkableReference<V> { private static class Pair<T> { final T reference; final boolean mark; private Pair(T reference, boolean mark) { this.reference = reference; this.mark = mark; } static <T> Pair<T> of(T reference, boolean mark) { return new Pair<T>(reference, mark); } } private volatile Pair<V> pair; public AtomicMarkableReference(V initialRef, boolean initialMark) { pair = Pair.of(initialRef, initialMark); } ...}public class AtomicStampedReference<V> { private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); } ...}
在Java1.8中还增加了
DoubleAccumulator
、DoubleAdder
、LongAccumulator
、LongAdder
这四个类用于累积计数。
- Java多线程复习与巩固(七)--原子性操作
- Java多线程复习与巩固(八)--volatile关键字与CAS操作
- Java多线程复习与巩固(一)--线程基本使用
- Java多线程复习与巩固(三)--线程同步
- Java多线程复习与巩固(四)--synchronized的实现
- Java多线程复习与巩固(六)--线程池ThreadPoolExecutor
- 多线程与原子操作
- volatile AtomicInteger java多线程操作 原子性
- Java原子操作与多线程操作测试一例
- Java语言基础复习与巩固
- Java多线程复习与巩固(二)--线程相关工具类的使用
- Java多线程复习与巩固(五)-生产者消费者问题(第一部分)
- Java多线程—原子性与可视性
- java 多线程:原子性与可视性
- 原子性与原子操作
- java 多线程:原子性
- java 多线程:原子性
- HTML复习与巩固
- HTML5 介绍(Video标签)
- Python编程要点-- socket 编程
- 视频教程强大合集
- 今日见君面,恰似故人归
- windows(64位)下使用curl命令
- Java多线程复习与巩固(七)--原子性操作
- 33 个 2017 年必须了解的 iOS 开源库(包含swift)
- MFC基于单文档分割视图后子窗口点击编辑框获得相应的操作
- 某校选拔赛
- HLS ORB算法设计心得
- Redis 集群详解 环境搭建 调用案例
- CocoaPods 安装过程中的 Warning&Error 备忘
- iOS图片拉伸之神属性:resizableImageWithCapInsets
- No enclosing instance of type is accessible.