首页 >> 大全

Java多线程之原子操作类

2023-10-03 大全 25 作者:考证青年

本文目录:

文章目录 包的使用

在并发编程中很容易出现并发安全问题,最简单的例子就是多线程更新变量i=1,多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过进行控制来达到线程安全的目的。但是由于是采用的是悲观锁策略,并不是特别高效的一种解决方案。实际上,在J.U.C下的包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新多种类型。包下的这些类都是采用乐观锁策略CAS来更新数据。 CAS原理与问题

CAS操作(又称为无锁操作)是一种乐观锁策略。它假设所有线程访问共享资源的时候不会出现冲突,因此不会阻塞其他线程的操作。那么,如果出现冲突了怎么办?无锁操作是使用CAS( and swap)来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

CAS的操作过程

举例说明:

包中的类,是通过类下的函数自旋来保证原子性,

其中函数调用的函数如下所示:

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;}

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

可见只有自旋实现更新数据操作之后,while循环才能够结束。

CAS的问题 自旋时间过长。由函数可知,自旋时间过长会对性能是很大的消耗。ABA问题。因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C,或使用ce工具类。 包的使用 原子更新基本类型

包中原子更新基本类型的工具类:

:以原子更新的方式更新;

:以原子更新的方式更新;

:以原子更新的方式更新Long;

这几个类的用法基本一致,这里以为例总结常用的方法

(int delta):以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;() :以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;(int ):将实例中的值更新为新值,并返回旧值;():以原子的方式将实例中的原值加1,返回的是自增前的旧值;

原理不再赘述,参考上文函数。

使用示例:

public class AtomicExample {private static AtomicInteger atomicInteger = new AtomicInteger(2);public static void main(String[] args) {System.out.println(atomicInteger.getAndIncrement());System.out.println(atomicInteger.incrementAndGet());System.out.println(atomicInteger.get());}
}
// 2 4 4

为了解决自旋导致的性能问题,JDK8在包中推出了类。采用的方法是,共享热点数据分离的计数:将一个数字的值拆分为一个数组。不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多;要得到这个数字的话,就要把这个值加起来。相比,并发量大大提高。

优点:有很高性能的并发写的能力

缺点:读取的性能不是很高效,而且如果读取的时候出现并发写的话,结果可能不是正确的

原子更新数组类型

包中提供能原子更新数组中元素的工具类:

:原子更新整型数组中的元素;

:原子更新长整型数组中的元素;

:原子更新引用类型数组中的元素

这几个类的用法一致,就以来总结下常用的方法:

(int i, int delta):以原子更新的方式将数组中索引为i的元素与输入值相加;(int i):以原子更新的方式将数组中索引为i的元素自增加1;(int i, int , int ):将数组中索引为i的位置的元素进行更新

与的方法基本一致,只不过在前者的方法中会多一个指定数组索引位i。

使用示例:

public class AtomicExample {private static int[] value = new int[]{1, 2, 3};private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);public static void main(String[] args) {//对数组中索引为2的位置的元素加3int result = integerArray.getAndAdd(2, 3);System.out.println(integerArray.get(2));System.out.println(result);}
}
// 6 3

原子更新引用类型

如果需要原子更新引用类型变量的话,为了保证线程安全,也提供了相关的类:

:原子更新引用类型;:原子更新引用类型里的字段;nce:原子更新带有标记位的引用类型;

java原子操作cas_java中原子操作_

使用示例:

public class AtomicExample {private static AtomicReference reference = new AtomicReference<>();public static void main(String[] args) {User user1 = new User("a", 1);reference.set(user1);User user2 = new User("b",2);User user = reference.getAndSet(user2);System.out.println(user);System.out.println(reference.get());}static class User {private String userName;private int age;public User(String userName, int age) {this.userName = userName;this.age = age;}@Overridepublic String toString() {return "User{" +"userName='" + userName + '\'' +", age=" + age +'}';}}
}
// User{userName='a', age=1}
// User{userName='b', age=2}

使用示例:

public class AtomicExample {public static void main(String[] args) {AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");Dog dog1 = new Dog();updater.compareAndSet(dog1, dog1.name, "cat");System.out.println(dog1.name);}
}class Dog {volatile String name = "dog1";
}

原子更新字段类型

如果需要更新对象的某个字段,同样也提供了相应的原子操作类:

ater:原子更新整型字段类;er:原子更新长整型字段类;

要想使用原子更新字段需要两步操作:

原子更新字段类型类都是抽象类,只能通过静态方法来创建一个更新器,并且需要设置想要更新的类和属性;

更新类的属性必须使用 进行修饰;

dater使用示例:

public class AtomicExample {private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");public static void main(String[] args) {User user = new User("a", 1);System.out.println(updater.getAndAdd(user, 5));System.out.println(updater.addAndGet(user, 1));System.out.println(updater.get(user));}static class User {private String userName;public volatile int age;public User(String userName, int age) {this.userName = userName;this.age = age;}@Overridepublic String toString() {return "User{" +"userName='" + userName + '\'' +", age=" + age +'}';}}
}

解决CAS的ABA问题

ce:原子更新引用类型,这种更新方式会带有版本号,从而解决CAS的ABA问题

ce使用示例:

public class AtomicExample {public static void main(String[] args) {Integer init1 = 1110;
//        Integer init2 = 126;AtomicStampedReference reference = new AtomicStampedReference<>(init1, 1);int curent1 = reference.getReference();
//        Integer current2 = reference.getReference();reference.compareAndSet(reference.getReference(), reference.getReference() + 1, reference.getStamp(), reference.getStamp() + 1);//正确写法
//        reference.compareAndSet(current2, current2+1, reference.getStamp(), reference.getStamp() + 1);//正确写法
//        reference.compareAndSet(1110, 1111, reference.getStamp(), reference.getStamp() + 1);//错误写法
//        reference.compareAndSet(curent1, curent1+1, reference.getStamp(), reference.getStamp() + 1);//错误写法
//        reference.compareAndSet(current2, current2 + 1, reference.getStamp(), reference.getStamp() + 1);System.out.println("reference.getReference() = " + reference.getReference());}
}

ce踩过的坑

参考上面的代码,分享一个笔者遇到的一次坑。ce的函数中,前两个参数是使用包装类的。所以当参数超过128时,而且传入参数并不是.()获取的话,会导致 == .为false,则无法进行更新。

public boolean compareAndSet(V   expectedReference,V   newReference,int expectedStamp,int newStamp) {Pair current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));}

哎呀,如果我的名片丢了。微信搜索“全菜工程师小辉”,依然可以找到我

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了