博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JUC中的原子类操作
阅读量:3959 次
发布时间:2019-05-24

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

JUC中原子类的操作

文章目录


前言

atomic译为原子,原子在化学中中表示物质最小的单位是不可分割。而在多线程中原子类(具有原子操作特性的类)中的操作是不可中断的。即时在多线程的情况下原子操作一旦开始不会受到其它线程的干扰。

一、简图

atomic中原子操作类简图

在这里插入图片描述

atomic原子操作得益于:JMM volatile UnSafe CAS机制

二、基本类型原子类

AtomicInteger

AtomicLong
AtomicBoolean

常用方法:

public final int get() //获取当前的值public final int getAndSet(int newValue)//获取当前的值,并设置新的值public final int getAndIncrement()//获取当前的值,并自增public final int getAndDecrement() //获取当前的值,并自减public final int getAndAdd(int delta) //获取当前的值,并加上预期的值boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

举例:自增

public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);}this表示当前对象valueOffset表示字段的偏移量1:表示自增的值//我们通过this+valueOffset来获取需要操作的字段位置

源码:

public final int getAndAddInt(Object var1, long var2, int var4) {
int var5; do {
var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }--------------------getIntVolatile(var1,var2);确保获取的值是最新的compareAndSwapInt()-->使用CAS机制:比较和交换,如果内存值与我们的预期值相等的话就返回true,并将最新的结果返回。如果不相等就返回false,继续此时的do while循环直至成功为止。

案例:

来100个线程每个线程将变量值自增100.
使用两种方法比较时间:
案例一:使用原子操作类:

import sun.misc.Unsafe;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;public class HahaTest {
//定义一个AtomicInteger对象 static AtomicInteger count = new AtomicInteger(); public static void main(String[] args) {
Long start = System.currentTimeMillis(); CountDownLatch countDownLatch = new CountDownLatch(100); for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override public void run() {
try{
for (int i1 = 0; i1 < 100; i1++) {
increase(); } }finally {
countDownLatch.countDown(); } } }).start(); } try {
countDownLatch.await(); Long end = System.currentTimeMillis(); System.out.println("总耗时"+(end-start)+"毫秒,最终的结果为"+count); } catch (InterruptedException e) {
e.printStackTrace(); } } public static void increase(){
count.getAndIncrement(); }}

结果:

在这里插入图片描述
案例二:使用synchronized同步锁:

import sun.misc.Unsafe;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;public class HahaTest {
//定义一个AtomicInteger对象 static int count = 0; public static void main(String[] args) {
Long start = System.currentTimeMillis(); CountDownLatch countDownLatch = new CountDownLatch(100); for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override public void run() {
try{
for (int i1 = 0; i1 < 100; i1++) {
increase(); } }finally {
countDownLatch.countDown(); } } }).start(); } try {
countDownLatch.await(); Long end = System.currentTimeMillis(); System.out.println("总耗时"+(end-start)+"毫秒,最终的结果为"+count); } catch (InterruptedException e) {
e.printStackTrace(); } } public static synchronized void increase(){
count++; }}

结果:

在这里插入图片描述
两个结果都是正确的,但是时间上使用原子类的相对较短。此次我们在自增的时候没有休眠,如果我们让程序休眠一会那么两者的差距就会变得十分明显。

三、数组类型的原子类

AtomicIntegerArray

AtomicLongArray
AtomicReferenceArray

常用方法:

public final int get(int i) //获取 index=i 位置元素的值public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValuepublic final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减public final int getAndAdd(int delta,int j) //获取 index=dalta 位置元素的值,并加上预期的值jboolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

案例:

模拟一个学校有5间教室,一共来个5批学生每批学生都有100人。看最后每个教室的人数

import sun.misc.Unsafe;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicIntegerArray;public class HahaTest {
//定义一个AtomicInteger对象 static AtomicIntegerArray room = new AtomicIntegerArray(5); public static void main(String[] args) {
Long start = System.currentTimeMillis(); CountDownLatch countDownLatch = new CountDownLatch(5); Thread thread = new Thread(new Runnable() {
@Override public void run() {
for (int i = 0; i < 5; i++) {
for (int i1 = 0; i1 < 100; i1++) {
increase(i); } countDownLatch.countDown(); } } }); thread.start(); try {
countDownLatch.await(); } catch (InterruptedException e) {
e.printStackTrace(); } for (int i = 0; i < 5; i++) {
System.out.println("第"+(i+1)+"间教室一共有"+room.get(1)+"名学生"); } } public static void increase(int i){
room.getAndIncrement(i); }}

结果:

在这里插入图片描述

该处使用的url网络请求的数据。


四、引用类型的原子操作类

AtomicReference :引用类型原子类    AtomicStampedRerence :原子更新引用类型里的字段原子类AtomicMarkableReference :原子更新带有标记位的引用类型
AtomicReference是对象引用的封装,可以保证对象引用在修改的时候线程安全

常用方法:

boolean compareAndSet(V expect, V update)        如果当前值 ==为预期值,则将值设置为给定的更新值。  V get()         获取当前值。  getAndSet(V newValue)       将原子设置为给定值并返回旧值。  V getAndUpdate(UnaryOperator
updateFunction) 用应用给定函数的结果原子更新当前值,返回上一个值。 void lazySet(V newValue) 最终设定为给定值。 void set(V newValue) 设置为给定值。

模拟案例:

一家商场为了吸引客户,凡是新用户的话就将赠送20元的优惠卷,每个人只能赠送一次。
代码模拟消费和充值的过程:

import java.util.concurrent.atomic.AtomicReference;public class ShopTest {
static int nowMoney = 19; static AtomicReference
atomic = new AtomicReference<>(nowMoney); public static void main(String[] args) {
chong(); xiao(); } //模拟两个线程同时充值赠送优惠卷 static void chong(){
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override public void run() {
//首先获取当前的money int money = atomic.get(); if( atomic.compareAndSet(money,money+20)){
System.out.println("为该用户赠送了20元优惠卷,用户余额"+atomic.get()); } try {
//模拟休眠,让另一个线程也去充值 Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } } }).start(); } } static void xiao(){
for (int i = 0; i < 5; i++) {
//获取当前的余额 int money = atomic.get(); if(money > 5){
if(atomic.compareAndSet(money,money-5)){
System.out.println("该用户消费了5元,此时余额"+atomic.get()+"元"); } } try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } } }}

结果:

在这里插入图片描述
上述问题其实就是我们说的ABA问题。我们知道在出现ABA问题的时候。我们可以使用版本号来解决。

ABA问题是CAS机制的一个最大弊端。CAS在使用时会先比较内存值V和预期值是否相等,若相等则将新值B保存。否则循环直至成功返回trueABA问题就是一个线程在比较时,另一个线程修改了值后又修改了一次。导致第一个线程在判断时比较为true,其实该值已经变化了A---B----A ===>该值其实变化了,但在CAS机制看来是一致的。

atomic包中AtomicStampedRerence也可以解决ABA问题

AtomicStampedReference在使用的时候除了要传递预期值和更新值外还需要传递期望时间戳和新的时间戳,只有期望时间戳和新的时间戳一致的话才进行新值的保存。当数值更改的话,除了修改数值外还需要修改时间戳。
//比较设置,参数依次为:期望值、写入新值、期望时间戳、新时间戳public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp);//获得当前对象引用public V getReference();//获得当前时间戳public int getStamp();//设置当前对象引用和时间戳public void set(V newReference, int newStamp);

上述代码修改:

import java.util.concurrent.atomic.AtomicReference;import java.util.concurrent.atomic.AtomicStampedReference;public class ShopTest {
static int nowMoney = 19; static AtomicStampedReference
atomic = new AtomicStampedReference<>(nowMoney,0); public static void main(String[] args) {
chong(); xiao(); } //模拟两个线程同时充值赠送优惠卷 static void chong(){
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override public void run() {
//首先获取当前的money int money = atomic.getReference(); int temp = atomic.getStamp(); if( atomic.compareAndSet(money,money+20,temp,temp+1)){
System.out.println("为该用户赠送了20元优惠卷,用户余额"+atomic.getReference()); } try {
//模拟休眠,让另一个线程也去充值 Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } } }).start(); } } static void xiao(){
for (int i = 0; i < 5; i++) {
//获取当前的余额 int money = atomic.getReference(); int temp = atomic.getStamp(); if(money > 5){
if(atomic.compareAndSet(money,money-5,temp,temp+1)){
System.out.println("该用户消费了5元,此时余额"+atomic.getReference()+"元"); } } try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } } }}

结果:

在这里插入图片描述

五、对象属性修改的原子类操作

AtomicIntegerFieldUpdater:原子更新整形字段的值AtomicLongFieldUpdater:原子更新长整形字段的值AtomicReferenceFieldUpdater :原子更新应用类型字段的值
修改对象属性的步骤:第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。

举例:AtomicReferenceFieldUpdater的使用

public static 
AtomicReferenceFieldUpdater
newUpdater( Class
tclass, Class
vclass, String fieldName)tclass:字段所属的类vclass:字段类型fieldName:字段名称

案例:

只有字段为false时才进行修改

import com.sun.org.apache.xpath.internal.operations.Bool;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;public class Demo {
static Demo demo = new Demo(); volatile Boolean aBoolean = Boolean.FALSE; AtomicReferenceFieldUpdater
atomic = AtomicReferenceFieldUpdater.newUpdater(Demo.class, Boolean.class,"aBoolean"); public synchronized void init(){
if(atomic.get(demo)){
System.out.println("该字段的值为true不需要修改"); } if(atomic.compareAndSet(demo,false,true)){
System.out.println("修改后的字段值为"+atomic.get(demo)); } } public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override public void run() {
demo.init(); } }).start(); } }}

结果:

在这里插入图片描述

上述代码中第一次调用init方法的时候,CAS判断时,内存中的值是false,和预期值一致,因此做了修改当别的线程调用init方法时,发现与预期值不一致,则放弃修改了。

转载地址:http://knxzi.baihongyu.com/

你可能感兴趣的文章
编写跨平台Java程序注意事项
查看>>
富人和穷人的12个经典差异
查看>>
java 注意事项[教学]
查看>>
MetaWeblogAPI测试
查看>>
软件配置管理概念-1,介绍
查看>>
软件配置管理概念-2,用户角色
查看>>
软件配置管理概念-3,CM系统的概念
查看>>
JSP/Servlet应用程序优化八法
查看>>
人生必修的181条佛理
查看>>
The Most Widely Used Java Libraries
查看>>
简单在单机使用apache-james(开源邮件服务器)
查看>>
lsof 快速起步
查看>>
使用ScribeFire方便地发布blog
查看>>
跨平台Java程序注意事项
查看>>
Python字符与数字的相互转换
查看>>
C 指针解读
查看>>
有关乱码的处理---中国程序员永远无法避免的话题
查看>>
JSP的运行内幕
查看>>
python超简单的web服务器
查看>>
代理模式、静态代理、动态代理、aop
查看>>