Android并发编程 原子类与并发容器

Android开发的漫漫长途上的一点感想和记录,如果能给各位看官带来一丝启发或者帮助,那真是极好的。

前言

上一篇博文中,主要说了些线程以及锁的东西,我们大多数的并发开发需求,基本上可以用synchronized或者volatile解决,虽然synchronized已经被JDK优化了,但有的时候我们还是觉得synchronized太重了,

比如说一个电影院卖票,这个票数是一定的而且共享的,我想尽快的卖票并且知道还有多少余票。在程序员看来这就是个票数自减以及获取最新票数的操作。

private static Long sCount = 10000L; final Object obj = new Object(); //这里开了1000个线程对sCount并发操作 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { synchronized (obj) { //这里加锁保证同步,使用synchronized总觉得没有 //必要,毕竟就是自减操作,如果不使用synchronized又有什么办法呢? sCount--; } } }).start(); } Thread.sleep(5000); System.out.println(sCount);

再有,我们平常使用的容器类List以及Map,如ArrayList、HashMap这些容器是非线程安全的,那我们如果需要支持并发的容器,我们该怎么办呢??读者莫急,这正是本篇分享的内容。

原子类

我们先来解决第一个问题,JDK1.5之后为我们提供了一系列的原子操作类,位于java.util.concurrent.atomic包下。

Android并发编程 原子类与并发容器

原子操作基本类型类

AtomicBoolean:原子更新布尔类型。

AtomicInteger:原子更新整型。

AtomicLong:原子更新长整型。

以上3个类提供的方法几乎一模一样,所以本篇仅以AtomicInteger为例进行讲解,
AtomicInteger的常用方法如下。

int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的
value)相加,并返回结果。

boolean compareAndSet(int expect,int update):如果当前值(调用该函数的值)等于预期值(expect),则以原子方式将当前值(调用该函数的值)设置为更新的值(update)。

int getAndIncrement():以原子方式将当前值加1,返回旧值。

int incrementAndGet()以原子方式将当前值加1,返回新值。

int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

那按照上面的知识重新对上面的卖票问题编程如下

private static AtomicLong sAtomicLong = new AtomicLong(10000L); //这里开了1000个线程对sCount并发操作 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { sAtomicLong.decrementAndGet(); } }).start(); } Thread.sleep(5000); System.out.println(sAtomicLong);

上面的是原子更新基本类型,那对于对象呢,JDK也提供了原子更新对象引用的原子类

原子更新引用类型

AtomicReference:原子更新引用类型。

AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类
型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef,boolean
initialMark)。

以上几个类提供的方法几乎一样,所以本节仅以AtomicReference为例进行讲解

boolean compareAndSet(V expect, V update):如果当前对象(调用该函数的对象)等于预期对象(expect),则以原子方式将当前对象(调用该函数的对象)设置为更新的对象(update)。

V get():获取找对象

void set(V newValue):设置对象

V getAndSet(V newValue):以原子方式将当前对象(调用该函数的对象)设置为指定的对象(newValue),并返回原来的对象(设置之前)

那这个东西用在哪里呢,我在著名的Rxjava源码中看到了原子更新对象的用法。

CachedThreadScheduler.java

//原子引用AtomicReference AtomicReference<CachedWorkerPool> pool; static final CachedWorkerPool NONE; static { NONE = new CachedWorkerPool(0, null); NONE.shutdown(); } public CachedThreadScheduler() { this.pool = new AtomicReference<CachedWorkerPool>(NONE); start(); } @Override public void start() { CachedWorkerPool update = new CachedWorkerPool(KEEP_ALIVE_TIME, KEEP_ALIVE_UNIT); //调用AtomicReference的compareAndSet方法 if (!pool.compareAndSet(NONE, update)) { update.shutdown(); } }

在创建线程调度器的时候把初始的工作线程池更新为新的工作线程池

AtomicReferenceFieldUpdater以原子方式更新一个对象的属性值

AtomicMarkableReference是带有标记的原子更新引用的类,可以有效解决ABA问题,什么是ABA问题,
我们就以上面的代码为例

假设pool.compareAndSet调用之前,pool内的对象NONE被更新成了update,然后又更新成了NONE,那么在调用pool.compareAndSet的时候还是会把pool内的对象更新为update,也就是说AtomicReference不关心对象的中间历程,这对于一些以当前对象是否被更改过为判断条件的特殊情境,AtomicReference就不适用了。

所以JDK提供了AtomicMarkableReference

那除了上面的原子更新引用类型之外,JDK还为我们提供了原子更新数组

原子更新数组

通过原子的方式更新数组里的某个元素,Atomic包提供了以下4个类。

AtomicIntegerArray:原子更新整型数组里的元素。

AtomicLongArray:原子更新长整型数组里的元素。

AtomicReferenceArray:原子更新引用类型数组里的元素。

AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下。

int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。

boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子
方式将数组位置i的元素设置成update值。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdjfz.html