JavaJUC之Atomic系列12大类实例讲解和原理分解-编程开发技术_第1页
JavaJUC之Atomic系列12大类实例讲解和原理分解-编程开发技术_第2页
JavaJUC之Atomic系列12大类实例讲解和原理分解-编程开发技术_第3页
JavaJUC之Atomic系列12大类实例讲解和原理分解-编程开发技术_第4页
JavaJUC之Atomic系列12大类实例讲解和原理分解-编程开发技术_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、java juc z atomic系列12大类实例讲解和原理分解-编程 开发技术java juc之atomic系列12大类实例讲 解和原理分解原文出处:xieyu_zy在java6以后我们不但接触到了 lock相关的锁,也接触到了很多更加乐观的原 子修改操作,也就是在修改时我们只需要保证它的那个瞬间是安全的即可,经过 相应的包装后可以再处理对象的并发修改,以及并发屮的aba问题,木文讲述 atomic系列的类的实现以及使用方法,其中包含:基本类:atomiclntcgcr> atomiclong> atomicboolcan;引用类型:atomicreference、atomicr

2、eference 的 aba 实例、atomicstampedrerence> atomicmarkablereference;数组类型:atomiclntegerarray、atomiclongarray、atomicreferencearray属性原子修改器(updater) : atomicintegerfieldupckiter> atomiclongficldupdatcrx atomicrefcrcnccficldupdater看到这么多类,你是否觉得很困惑,其实没什么,因为你只需耍看懂一个,英余 的方法和使用都是大同小异的,相关的类会介绍他们之间的区别在哪里,在使用

3、屮需要注意的地方即可。在使用atomic系列前,我们需要先知道一个东西就是unsafe类,全名为: sun. misc. unsafe,这个类包含了大量的对c代码的操作,包括很多直接内存分 配以及原子操作的调用,而它z所以标记为非安全的,是告诉你这个里而大量的 方法调用都会存在安全隐患,需要小心使用,否则会导致严重的后果,例如在通 过unsafe分配内存的吋候,如果自己指定某些区域可能会导致一些类似c+ 样的指针越界到其他进程的问题,不过它的具体使用并不是本文的重点,本文重 点是atomic系列的内容大多会基于unsafe类中的以卜'几个本地方法來操作:对象的引用进行对比后交换,交换成

4、功返回true,交换失败返回false,这个 交换过程完全是原了的,在cpu上计算完结果后,都会对比内存的结果是否还是 原先的值,若不是,则认为不能替换,因为变量是volatile类型所以最终写入 的数据会被其他线程看到,所以一个线程修改成功后,其他线程就发现自c修改 失败了。参数对彖所在的类本身的对象(一般这里是对一个对彖的屈性做修改,才会 出现并发,所以该对彖所存在的类也是有一个对彖的)参数2:这个屈性在这个对彖里而的相对便宜量位置,其实对比时是对比内存单 元,所以需要属性的起始位置,而引用就是修改引用地址(根据os、vm位数和 参数配置决定宽度-般是4-8个字节),int就是修改相关的4

5、个字节,而long 就是修改相关的8个字节。获取偏移量也是通过unsafe的一个方法:objectficlcloffset (ficlclficlcl)来获 取属性在对象屮的偏移量;静态变量需要通过:staticfieldoffset (field field) 获取,调用的总方法是:fieldoffset (fieldfield)参数3:修改的引用的原始值,用于对比原来的引用和要修改的目标是否一致。参数4:修改的口标值,要将数据修改成什么。public final native boolean compareandswapobject(object paramobjectl, long pa

6、ramlong, object param0bject2, object param0bjcct3);public final native boolean compareandswapint(object paramobject, long paramlong, int paraminll, ini paramtnt2);#对long的操作,要看vm是否支持对long的cas,因为有可能vm本身不支持, 若不支持,此时运算会变成lock方式,不过现在vm都基本是支持的而已。public final native boolean compareandswaplong(object paramo

7、bject, long paramlongl, long paramlong2, long paramlong3);我们不推荐直接使用unsafe来操作原子变量,而是通过java封装好的一些类来 操作原子变量。实例代码 1: atomicintegertest. javaimport java uti1.concurrent.atomic atomiclnteger;public class atomicintegertest /*常见的方法列表直接返hl值增加指定的数据,返冋变* see atomidnteger#get()* see atomiclnteger#getandadd(int)

8、 化前的数据* see atomictntegertfgetanddecreinent ()减少 1,返回减少询的数 据* see atomicintegerftgetandlncrement ()增加 1,返回增加前的数 据* see atomicintegeriigetandset (int) 设置指定的数据,返冋设 置前的数据减少1,返冋减少后的值增加1,返回增加后的值 仅仅当get时才会set加后的数据* see atomicinteger#decreinentandget ()* see atomictntegeriiincrementandget ()* scc atomiclnt

9、cgcr#lazysct(int)* see atomicintegeriicompareandset (int, int)尝试新增后对比, 若增加成功则返冋true否则返冋false*/public final static atomictnteger testinteger 二 new atomicintcgcr(l);public static void main(string args) throwsinterruptedexception final thread threads 二 new thread10;for(int i = 0 ; i < 10 ; i+) final

10、 int num = i;threadsi = new thread() public void run() try thread.sleep (1000); catch (interruptedexception e) e. printstacktrace ();int now =test_1nteger. incrementandget();system, out. printin (我是线程: + num + ,我得到值了,增加后的值为: + now);;threadsi start ();for(thread t : threads) t. join();system, out. pr

11、inting最终运行结果: +test_integer. get();代码例子小模拟多个线程并发对atomiclnteger进行增加1的操作,如果这个数 据是普通类型,那么增加过程屮出现的问题就是两个线程可能同时看到的数据都 是同一个数据,增加完成后写冋的时候,也是同一个数据,但是两个加法应当串行增加1,也就是加2的操作,其至于更加特殊的情况是一个线程加到3后,写 入,另一个线程写入了 2,还越变越少,也就是不能得到正确的结果,在并发下, 我们模拟计数器,要得到精确的计数器值,就需要使用它,我们希望得到的结果 是11,可以拷贝代码进去运行后看到结果的确是11,顺然输出的顺序可能不一 样,也同时

12、可以证明线程的确是并发运行的(只是在输出的时候,征用 system, out这个对象也不一定是谁先抢到),但是最终结果的确是11。和信你对atomiclnteger的使用有一些了解了吧,要知道更多的方法使用,请参 看这段代码中定义变量位置的注释,有关于atomictnteger的相关方法的详细注 释,可以一直接跟踪进去看源码,注释中使用了简单的描述说明了方法的用途。而对于atomiclong呢,其实和atomiclnteger差不多,唯一的区别就是它处理 的数据是long类型的就是了;对于atomicboolean呢,方法要少一些,常见的方法就两个:atomicbooleanwcomparea

13、ndset (boolean, boolean) 第一个参数为原始值,第 二个参数为要修改的新值,若修改成功则返回true,否则返回false atomicbooleanttgetandset (boolean) 尝试设置新的 boolean 值,直到成功为 止,返回设置前的数据因为boolean值就两个值,所以就是来冋改,相对的很多增加减少的方法自然就 没有了,对于使用来讲,我们列举一个boolean的并发修改,仅有一个线程可以 修改成功的例子:实例代码 2: atomicbooleantest. javaimport java uti1.concurrent atomic atomicbo

14、olean;public class atomicbooleantest /*主要方法:* see atomicbooleanticonipareandset(boolean, boolean) 第一个 参数为原始值,第二个参数为要修改的新值,若修改成功则返冋true,否则返 回 false* see atomicbooleanttgetandset (boolean)尝试设置新的 boolean值,直到成功为止,返回设置前的数据*/public final static atomicboolean test_boolean 二 new atomicboolean();public stati

15、c void main(string args) for (int i 二 0 ; i 10 ; i+) new thread () public void run() try thread.sleep(1000); catch (tnterruptedexception e)e. printstacktrace ();if(test boolean. compareandset (false, true) system. out. printin(我 成功了! );. start ();这里有10个线程,我们让他们儿乎同时去征用boolean值的修改,修改成功者 输岀:我成功了!此吋你运行完

16、你会发现只会输出一个“我成功了!”,说明征 用过程中达到了锁的效果。那么几种基本类型就说完了,我们來看看里面的实现是不是如我们开始说的 unsafe那样,看几段源码即可,我们看t atomiclnteger的一些源码,例如开 始用的:incrementandget方法,这个,它的源码是:public final int incrementandget() for (;) int current = get ();int next = current + 1;if (compareandset(current, next)return next;可以看到内部有一个死循环,只有不断去做compar

17、eandset操作,直到成功为止, 也就是修改的根木在compareandset方法里面,可以去看下和关的修改方法均是 这样实现,那么看下compareandset方法的body部分是:public final boolean compareandset(int expect, int update) return unsafe. comparcandswapint(this, valucoffsct, cxpcct, update);可以看到这里使用了 unsafe的compareandswaplnt的方法,很明显this就是指 atomiclnteger当前的这个对彖(这个对彖不用像上面说

18、的它不能是static和 final,它无所谓的),而valueoffset的定义是这样的:private static final long valueoffset;static try valueoffset = unsafe. objectfieldoffset(atomiclnteger. class. getdeclaredfield(z,value,z); catch (exception ex) throw new error(ex); 可以看出是通过我们前面所述的objectfieldoffset方法来获取的属性偏移量, 所以你自己如果定义类似的操作的吋候,就要注意,这个属性不

19、能是静态的,否 则不能用这个方法來获取。后面两个参数口然是对比值和需要修改的目标对象的地址。其实atomic系列你看到这里,ja它层面你就知道差不多了,其余的就是特殊用 法和包装而已,刚才我们说了 unsafe的3个方法无非是地址和值的区别在内存 层面是没有木质区别的,因为地址木身也是数字值。为了说明这个问题,我们就先说reference的使用:我们测试一个reference,和boolean测试方式一样,也是测试多个线程只有一 个线程能修改它。实例代码 1: atomicreferencetest. javaimport java. util, concurrent. atomic. ato

20、micreferenee;public class atomicreferencctcst /*相关方法列表* see atomi creferenceiicompareandset (object, object)对比设置 值,参数1:原始值,参数2:修改目标引用* see atomicreferencettgetandset (object)将引用的目标修改为设 置的参数,直到修改成功为止,返冋修改前的引用*/public final static atomicreference <string>atomtc_reference 二 new atomicrcfcrcnce<

21、;string> (,abc,3 ;public static void main(string args) for (int i = 0 ; i < 100 ; i+) final int num 二 i;new thread () public void run() try thread, sleep (math, abs (int) (math, random () * 100); catch (tnterruptedexception e)c. printstacktrace ();if (atomic reference. compareandset (/zabc/z,

22、new string(z,abcz,) system. out. piintln(我 是线程: + num + ,我获得了锁进行了对象修改! );. start ();测试结果如我们所料,的确只有一个线程,执行,跟着代码:compareandset进 去,发现源码中的调用是:public final boolean compareandset(v expect, v update) rcturn unsafc. comparcandswapobjcct (this, valueoffsct, expect, update);ok,的确和我们上面所讲一致,那么此时我们又遇到了引用修改的新问题,什

23、么 问题呢? aba问题,什么是aba问题呢,当某些流程在处理过程中是顺向的,也 就是不允许重复处理的情况卜,在某些情况下导致一个数据由a变成b,再中间 可能经过0-n个环节后变成了 a,此时a不允许再变成bt,因为此时的状态已 经发生了改变,例如:银行资金里面做一批账目操作,要求资金在80-100元的 人,增加20元钱,吋间持续一天,也就是后台程序会不断扫描这些用户的资金 是否是在这个范围,但是要求壇加过的人就不能再壇加了,如果壇加20后,被 人取出10元继续在这个范围,那么就可以无限套现出來,就是aba问题了,类 似的述冇抢红包或屮奖,比如每天每个人限量3个红包,屮那个等级的奖的个数 等等

24、。此时我们需耍使用的方式就不是简单的compareandset操作,凶为它仅仅是考虑 到物理上的并发,而不是在业务逻辑上去控制顺序,此时我们需要借鉴数据库的 事务序列号的一些思想来解决,假如每个对象修改的次数可以记住,修改前先对 比下次数是否一致再修改,那么这个问题就简单了,atomicstampedreference 类正是提供这一功能的,其实它仅仅是在atomicreference类的再一次包装,里 面增加了一层引用和计数器,其实是否为计数器完全由自己控制,大多数我们是 让他自増的,你也可以按照自己的方式来标示版本号,下面一个例子是aba问题 的简单演示:实例代码3 (aba问题模拟代码演

25、示):import java.util.concurrent, atomic. atomicreference;* aba问题模拟,线程并发中,导致aba问题,解决方案是使用 atomicmarkablcrcfcrcncc* 请参看相应的例子:atomicstampedreferencetest>atomicmarkablereferencetest*/public class atomicrcfcrcnccabatcst public final static atomicreference string>atomic_reference = new atomicreferenc

26、estring>(abc);public static void main (string args) for (int i = 0 ; i < 100 ; i+) final int num = i;new thread () publ ic void run () try thread, sleep (math, abs (int) (math, random () * 100); catch (interruptedexception e) c. printstacktrace ();if (atomic_reference. compareandset (/zabc/z ,

27、 abc2)system. out. printin(我 是线程: + num + ,我获得了锁进行了对象修改! );. start ();new thread () public void run() while(!atomic_reference. compareandset(z,abc2z,, abc);system, out. printin (z/已经改为原始值! “); start ();代码中和原来的例子,唯一的区别就是最后增加了一个线程让他将数据修改为原 来的值,并一一直尝试修改,一直到修改成功为止,为什么没有一直接用:方法呢 getandset方法呢,因为我们的目的是要让某个

28、线程先将他修改为abc2后再让 他修改回qbc,所以需要这样做;此时我们得到的结果是:我是线程:41,我获得了锁进行了对彖修改!已经改为原始值!我是线程:85,我获得了锁进行了对象修改!当然你的线程编号多半和我不一样,只耍征用到就对,可以发现,有两个线程修 改了这个字符串,我们是想那一堆将abc改成abc2的线程仅冇一个成功,即使 其他线程在他们征用时将其修改为abc,也不能再修改。此时我们通过类来atomicstampedreference解决这个问题:实例代码 4 (atomicstampedreference 解决 aba 问题):import java uti1.concurrent.

29、 atomic atomicstampedrcfcrcnco;public class atomicstampedreferencetest public final static atomicstampedreference<string>atomic_reference = new atomicstampedrcfcrcnce<string> (,abc,/ , 0);public static void main(string args) for(int i = 0 ; i < 100 ; i+) final int num = i;final int st

30、amp = atomic_reference. getstampo ; new thread () public void run() try thread, sleep (math, abs (int) (math, random () * 100); catch (interruptedexception e)c. printstacktrace ();if (atomicreference. compareandset (/zabc/z , abc2,st amp , stamp + 1) -system, out. printin(我 是线程: + num + ,我获得了锁进行了对象修

31、改! );. start ();new thread () public void run() int stamp = atomic reference. getstamp();while(!atomtc_reference. compareandset (,abc2,z, abc,stamp , stamp + 1);system, out. printin (z,已经改回为原始值! );. start ();此时再运行程序看到的结果就是我们想要的了,发现将abc修改为abc2的线程 仅有一个被访问,虽然被修改回了原始值,但是其他线程也不会再将abc改为 abc2o而类:atomicmark

32、ab 1 ereference 和 atomicstampedreference 功能差不多,有 点区别的是:它描述更加简单的是与否的关系,通常aba问题只冇两种状态,而 atomicstampedreference是多种状态,那么为什么还要有atomicmarkab 1 ereference呢,因为它在处理是与否上面更加具有可读性,而 atomicstampedreference过于随意定义状态,并不便于阅读人量的是和否的关 系,它可以被认为是一个计数器或状态列表等信息,jewel提侣通过类名知道其 意义,所以这个类的存在也是必要的,它的定义就是将数据变换为true | false 如下:p

33、ublic final static atomicmarkablereferenee <string>atomtc_markable_reference 二 newatomicmarkablcrcfercnce<string>(,abc,/ , false);操作时使用:atomic_markable_reference. compareandset cabc", abc2,false, true);好了,reference的三个类的种类都介绍了,我们下面要开始说atomic的数组 用法,因为我们开始说到的都是一些简单变量和基本数据,操作数组呢?如果你 來设计

34、会怎么设计atomic的数组要求不允许修改长度等,不像集合类那么丰 富的操作,不过它可以让你的数组上每个元素的操作绝对安全的,也就是它细化 的力度还是到数组上的元素,为你做了二次包装,所以如果你来设计,就是在原 有的操作上增加一个下标访问即可,我们来模拟一个integer类型的数组,b|j: atomiclntegerarray实例代码 5 (atomicintegerarraytest. java)import java.util, concurrent, atomic. atomicintcgcrarray;public class atomicintegerarraytest /*常见的

35、方法列表* see atomicintegerarray#addandget (int, int)执行加法,第一 个参数为数组的下标,第二个参数为增加的数量,返回增加后的结果* see atomictntegerarrayttcompareandset(int, int, int) 对比修 改,参数1:数组下标,参数2:原始值,参数3,修改目标值,修改成功返冋 true 否则 false* see atomicintegerarray#decrementanclget (int)参数为数组下 标,将数组对应数字减少1,返冋减少后的数据* see atomictntegerarrayttincr

36、ementandget(int)参数为数组下 标,将数组对应数字増加1,返冋増加后的数据* see atomicintegerarray#getandadd (int, int) 和 addandget 类 似,区别是返回值是变化前的数据* see atomic!ntegerarrayttgetanddecrement(int)和 decrementandget类似,区别是返冋变化前的数据* see atomiclntcgcrarraytigctandlncrcmcnt (int)和 incrementandget类似,区别是返回变化前的数据* see atomicintegerarraytt

37、getandset (int, int)将对应下标的数 字设置为指定值,第二个参数为设置的值,返回是变化前的数据*/private final static atomicintcgcrarray atomic integer array = new atomiclntegerarray(new int 1, 2, 3, 4, 5, 6, 7, & 9, 10);public static void main(string jargs) throws interruptedexception thread threads = new thread100;for(int i = 0 ; i

38、 < 100 ; i+) final int index = i % 10;final int threadnum 二 i;threadsei = new thread() public void run() int resuit 二 atomic_integer_array. addandget(index, index + 1);system, out. printin (,z线程编号为: "+ threadnum + “,对应的原始值为: + (index + 1) + “,増加后的结果 为:"+ result);;threadsi. start ();for(

39、thread thread : threads) thrcad. join ();system, out println(,/=>n 执彳亍 已经完成,结果列表:/z);for(int i = 0 ; i < atomtc_tnteger_array. length() ; i+)system, out. prin11n(atomic_integer_array.get (i); - -计算结果说明:100个线程并发,每10个线程会被并发修改数组屮的一个元索, 也就是数组中的每个元素会被10个线程并发修改访问,每次增加原始值的大小, 此时运算完的结果看最后输岀的敲好为原始值的11倍

40、数,和我们预期的一致, 如果不是线程安全那么这个值什么都有可能。而相应的类:atomiclongarray其实和atomicintegerarray操作方法类似,最 大区别就是它操作的数据类型是long;而atomicrerencearray也是这样,只是 它方法只有两个:atomicreferencearray#compareandset (int, object, object)参数1:数组下标;参数2:修改原始值对比;参数3:修改目标值修改成功返冋true,否则返冋falseatomicreferencearrayttgetandset(int, object) 参数1:数组卜标参数2:

41、修改的目标修改成功为止,返回修改前的数据 到这里你是否对数组内部的操作应该有所了解了,和当初预期一样,参数就是多 了一个下标,为了完全验证这点,跟踪到源码中可以看到:public final int addandget(int i, int delta) while (true) int current = get (i);int next = current + delta;if (compareandset(i, current, next)return next;可以看到根据get(i)获取到对应的数据,然后做和普通atomiclnteger差不多 的操作,get操作里面有个细节是:pu

42、blic final int get(int i) return unsafegetlntvolat订e(array, rawindex(i);这里通过了 unsafe获取基于volatile方式获取(可见性)获取一个int类型的 数据,而获取的位置是由rawindex来确定,它的源码是:private long rawindex(int i) if (i <0 | i >二 array, length)throw new indcxoutofboundsexccption("indcx " + i);return base + (long) i * scale

43、;可以发现这个结果是一个地址位置,为base加上一耳光偏移量,那么看看base 和scale的定义为:private static final int base = unsafc.arraybascoffsct(intclass); private static final int scale = unsafe.arrayindexscale(intclass);可以发现unsafe里面提供了对数组base的位置的获取,因为对彖是有头部的, 而数组述有一个长度位置,第二个很明显是一个数组元素所占用的宽度,也就是 基本精度;这里应该口j以体会到unsafe所带來的强大了吧。本文最后要介绍的部分为

44、updater也就是修改器,它算是atomic的系列的一个 扩展,atomic系列是为你定义好的一些对象,你可以使用,但是如果是别人已 经在使用的对象会原先的代码需要修改为atomic系列,此时若全部修改类型到 对应的对象相信很麻烦,因为牵涉的代码会很多,此时java提供一个外部的 updater 以对对象的属性本身的修改提供类似atomic的操作,也就是它对这 些普通的屈性的操作是并发下安全的,分别由:atomicintegerfieldupdater> atomiclongfieldupdater> atomicreferenceupdater,这样操作后,系统会更 加灵活,也

45、就是可能那些类的属性只是在某些情况下需要控制并发,很多吋候不 需要,但是他们的使用通常有以下几个限制:限制1:操作的h标不能是static类型,前而说到unsafe的已经可以猜测到它 提取的是非static类型的属性偏移量,如果是static类型在获取时如果没右使 用对应的方法是会报错的,而这个updater并没有使用对应的方法。限制2:操作的目标不能是final类型的,因为final根本没法修改。限制3:必须是volatile类型的数据,也就是数据木身是读一致的。限制4:属性必须对当前的updater所在的区域是可见的,也就是private如果 不是当前类肯定是不可见的,protected如

46、果不存在父子关系也是不可见的, default如果不是在同一个package下也是不可见的。实现方式:通过反射找到属性,对属性进行操作,但是并不是设置accessable, 所以必须是可见的属性才能操作。说了这么多,來个实例看看吧。实例代码 6: (atomicintegerfieldupdatertest. java)import java utii.concurrent. atomic atomiclntegerfieldupdater;public class atomiclntegerfieldupdatertest static class a volatile int intvalue = 100;/*可以直接访问

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论