Java中12个原子操作类

Java 从 JDK 1.5 开始提供了 java.util.concurrent.atomic 包(以下简称Atomic包),这个包中的 原子操作类 提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。

因为变量的类型有很多种,所以在 Atomic 包里一共提供了 12个 类,属于以下 4 种类型的原子更新方式:

原子更新基本类型。

AtomicBoolean:原子更新布尔类型。

AtomicInteger:原子更新整型。

AtomicLong:原子更新长整型。

原子更新数组。

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

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

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

原子更新引用。

AtomicReference:原子更新对象引用。

AtomicMarkableReference:原子更新带有标记位的对象引用。

AtomicStampedReference:原子更新带有版本号的对象引用。

原子更新属性(字段)。

AtomicIntegerFieldUpdater:原子更新volatile修饰的整型的字段的更新器。

AtomicLongFieldUpdater:原子更新volatile修饰的长整型字段的更新器。

AtomicReferenceFieldUpdater:原子更新volatile修饰的引用类型里的字段的更新器。

Atomic 包里的类基本都是使用 Unsafe 实现的包装类。

原子更新基本类型

AtomicBoolean:原子更新布尔类型。

AtomicInteger:原子更新整型。

AtomicLong:原子更新长整型。

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

AtomicInteger 的常用方法如下:

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

boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。

int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。

void lazySet(int newValue):最终会设置成 newValue,使用 lazySet 设置值后,可导致其他线程在之后的一小段时间内还是可以读到旧的值。

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

示例

public static void main(String[] args) {AtomicInteger ai = new AtomicInteger(2);System.out.println("ai.get() = " + ai.get());System.out.println("ai.addAndGet(5) = " + ai.addAndGet(5));System.out.println("ai.get() = " + ai.get());System.out.println("ai.compareAndSet(ai.get(), 10) = " + ai.compareAndSet(ai.get(), 10));System.out.println("ai.get() = " + ai.get());System.out.println("ai.getAndIncrement() = " + ai.getAndIncrement());System.out.println("ai.get() = " + ai.get());ai.lazySet(8);System.out.println("ai.lazySet(8)");System.out.println("ai.get() = " + ai.get());System.out.println("ai.getAndSet(5) = " + ai.getAndSet(5));System.out.println("ai.get() = " + ai.get());
}

输出

ai.get() = 2
ai.addAndGet(5) = 7
ai.get() = 7
ai.compareAndSet(ai.get(), 10) = true
ai.get() = 10
ai.getAndIncrement() = 10
ai.get() = 11
ai.lazySet(8)
ai.get() = 8
ai.getAndSet(5) = 8
ai.get() = 5

AtomicInteger 的 getAndIncrement()方法:

public final int getAndIncrement() {for (; ; ) {int current = get();int next = current + 1;if (compareAndSet(current, next)) {return current;}}
}
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

for 循环体的先取得 AtomicInteger 里存储的数值

对 AtomicInteger 的当前数值进行 +1 操作,

关键是调用 compareAndSet 方法来进行原子更新操作,该方法先检查 当前数值是否等于current ?

	等于意味着 AtomicInteger 的值没有被其他线程修改过,则将 AtomicInteger 的当前数值更新成 next的值。如果不等 compareAndSet 方法会返回 false,程序会进入 for 循环重新进行 compareAndSet 操作。

Atomic 包提供了 3 种基本类型的原子更新,但是 Java 的基本类型里还有 char、float 和 double 等。

那么问题来了,如何原子的更新其他的基本类型呢?

Atomic包里的类基本都是使用 Unsafe 实现的,让我们一起看一下Unsafe的源码:

/*** 如果当前数值是expected,则原子的将Java变量更新成x** @return 如果更新成功则返回true*/
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);public final native boolean compareAndSwapInt(Object o, long offset,int expected, int x);public final native boolean compareAndSwapLong(Object o, long offset,long expected, long x);

综合上述代码,我们可以发现 Unsafe 只提供了 3 种 CAS 方法:compareAndSwapObject、compareAndSwapInt 和 compareAndSwapLong,再看 AtomicBoolean 源码,发现它是先把 Boolean 转换成 整型,再使用 compareAndSwapInt 进行 CAS,所以原子更新 char、float 和 double 变量也可以用类似的思路来实现。

原子更新数组

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

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

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

以上几个类提供的方法几乎一样,所以仅以 AtomicIntegerArray 为例进行介绍:
AtomicIntegerArray 类主要是提供原子的方式更新数组里的整型。

常用方法如下:

	int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

示例

public static void main(String[] args) {int[] value = new int[]{1, 2};AtomicIntegerArray ai = new AtomicIntegerArray(value);System.out.println("ai.getAndSet(0, 3)");ai.getAndSet(0, 3);System.out.println("ai.get(0) = " + ai.get(0));System.out.println("value[0] = " + value[0]);ai.compareAndSet(1, 2, 5);System.out.println("ai.compareAndSet(1, 2, 5)");System.out.println("ai.get(1) = " + ai.get(1));
}

输出

ai.getAndSet(0, 3)
ai.get(0) = 3
value[0] = 1
ai.compareAndSet(1,2,5)
ai.get(1) = 5

注意:数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行 修改 时,不会影响传入的数组。

原子更新引用

AtomicReference:原子更新对象引用。

AtomicMarkableReference:原子更新带有标记位的对象引用。

AtomicStampedReference:原子更新带有版本号的对象引用。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA问题。

我们以AtomicReference为例进行介绍。

示例

public class AtomicReferenceTest {public static AtomicReference<User> atomicUserRef = newAtomicReference<User>();public static void main(String[] args) {User user = new User("103style", 20);atomicUserRef.set(user);System.out.println("atomicUserRef.get() = " + atomicUserRef.get().toString());User updateUser = new User("xiaoke", 22);atomicUserRef.compareAndSet(user, updateUser);System.out.println("atomicUserRef.compareAndSet(user, updateUser);");System.out.println("atomicUserRef.get() = " + atomicUserRef.get().toString());}static class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}@Overridepublic String toString() {return "name='" + name + ", age=" + age;}}
}

输出

atomicUserRef.get() = name='103style, age=20
atomicUserRef.compareAndSet(user, updateUser);
atomicUserRef.get() = name='xiaoke, age=22

原子更新属性(字段)

AtomicIntegerFieldUpdater:原子更新volatile修饰的整型的字段的更新器。

AtomicLongFieldUpdater:原子更新volatile修饰的长整型字段的更新器。

AtomicReferenceFieldUpdater:原子更新volatile修饰的引用类型里的字段的更新器。

要想原子地更新字段类需要两步:

	因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。更新类的字段(属性)必须使用public volatile修饰符。

我们以AstomicIntegerFieldUpdater 为例进行讲解。

示例

public class AtomicIntegerFieldUpdaterTest {public static void main(String[] args) {// 创建原子更新器,并设置需要更新的对象类和对象的属性AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");// 设置柯南的年龄是10岁User conan = new User("conan", 10);// 柯南长了一岁,但是仍然会输出旧的年龄System.out.println(a.getAndIncrement(conan));// 输出柯南现在的年龄System.out.println(a.get(conan));}public static class User {public volatile int age;private String name;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}}
}

输出

10
11

了解更多欢迎点赞关注的哟!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/565339.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

swoole 捕捉php错误,swoole怎么处理错误

在协程编程中可直接使用try/catch处理异常。但必须在协程内捕获&#xff0c;不得跨协程捕获异常。不仅是应用层throw的Exception&#xff0c;底层的一些错误也是可以被捕获的&#xff0c;如function、class、method不存在下面的代码中&#xff0c;try/catch和throw在不同的协程…

【OpenCV 例程200篇】235. 特征提取之主成分分析(sklearn)

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】235. 特征提取之主成分分析&#xff08;sklearn&#xff09; 特征提取是指从原始特征中通过数学变换得到一组新的特征&#xff0c;以降低特征维数&#xff0c;消除相关性&#xff0c;减少无用信息…

Linux 进程资源分配,linux 进程管理和内存分配

1、进程相关概念进程&#xff1a;正在运行中的程序内核功用&#xff1a;进程管理、文件系统、网络功能、内存管理、驱动程序、安全功能等Process&#xff1a;运行中的程序的一个副本&#xff0c;是被载入内存的一个指令集合进程 ID(Process ID&#xff0c;PID)号码被用来标记各…

【OpenCV 例程300篇】234. 特征提取之主成分分析(PCA)

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程300篇】234. 特征提取之主成分分析&#xff08;PCA&#xff09; 5.1 特征提取的方法 初步获取的图像特征维数通常很大&#xff0c;而且往往包含一定的无关或冗余特征。特征提取是指从原始特征中通过数学变…

Java并发编程基础

线程的简介 什么是线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流&#xff0c;一个进程中可以并发多个线程&#xff0c;每条线程并行执行不同的任务。 为什么要使用多线…

【OpenCV 例程200篇】236. 特征提取之主成分分析(OpenCV)

『youcans 的 OpenCV 例程200篇 - 总目录』 【youcans 的 OpenCV 例程200篇】236. 特征提取之主成分分析&#xff08;OpenCV&#xff09; 文章目录【youcans 的 OpenCV 例程200篇】236. 特征提取之主成分分析&#xff08;OpenCV&#xff09;5.2 主成分分析的数学方法5.4 OpenC…

linux 访问共享内存,Linux下的共享内存(03)---通过指针访问共享内存中的数据...

环境&#xff1a;Vmware Workstation&#xff1b;CentOS-6.4-x86_64说明&#xff1a;1、将共享内存挂载至进程&#xff1a;void *shmat(int shmid, const void *shmaddr,int shmflg);参数shmid是要附加的共享内存区标示符。总是把参数shmaddr设为0。参数shmflg可以为SHM_RDON…

Java中锁的使用和实现

首先&#xff0c;我们要了解一个概念&#xff0c;JAVA中的锁到底是什么呢&#xff1f; 锁是用来控制多个线程访问共享资源的方式&#xff0c;一般来说&#xff0c;一个锁能够防止多个线程同时访问共享资源。 Lock接口 在Java SE 5之后&#xff0c;并发包中新增了Lock接口&am…

【OpenCV 例程200篇】225. 特征提取之傅里叶描述子

『youcans 的 OpenCV 例程200篇 - 总目录』 【youcans 的 OpenCV 例程200篇】225. 特征提取之傅里叶描述子 目标特征的基本概念 通过图像分割获得多个区域&#xff0c;得到区域内的像素集合或区域边界像素集合。我们把感兴趣的人或物称为目标&#xff0c;目标所处的区域就是目…

【OpenCV 例程300篇】238. OpenCV 中的 Harris 角点检测

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】238. OpenCV 中的 Harris 角点检测 角是直线方向的快速变化。角点通常被定义为两条边的交点&#xff0c;或者说角点的邻域应该具有两个不同区域的不同方向的边界。 角是高度有效的特征。角点检测&…

Redis Jedis

相信大家有着同样的问题&#xff0c;Jedis是什么呢&#xff1f;下面为你详细解答关于Redis Jedis的内容。 Jedis到底是什么&#xff1f; 在常见命令中&#xff0c;使用各种Redis自带客户端的命令行方式访问Redis服务。 而在实际工作中却需要用到Java代码才能访问&#xff0c;…

基础线性规划实现(matlab,lingo)

目录 一、本次所需解的问题 二、matlab解题 1&#xff09;语法 2&#xff09;数学思维 3&#xff09;matlab解题 运行结果&#xff1a; 三、lingo解题 lingo解题如下&#xff1a; 运行结果&#xff1a; 最后&#xff1a; 一、本次所需解的问题 需解出下面该线性规划问…

cwntos linux kde桌面,Centos如何安装KDE的桌面

其实KDE其实在初次安装系统的时候就可以选择&#xff0c;我下面是进入系统后的安装方法&#xff1a;第一步&#xff1a;检查KDE首先查看自己是否安装了KDE# yum grouplist在grouplist的输出结果中的“Installed Groups:”部分中&#xff0c;如果你能找到“X Window System”和“…

【OpenCV 例程 300篇】239. Harris 角点检测之精确定位(cornerSubPix)

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】239. Harris 角点检测之精确定位&#xff08;cornerSubPix&#xff09; 角是直线方向的快速变化。角点通常被定义为两条边的交点&#xff0c;或者说角点的邻域应该具有两个不同区域的不同方向的边界…

Redis Spring集成

近来Spring可谓是火了一把&#xff0c;那麽我们应该进行Redis缓存和Spring集成的呢&#xff1f;&#xff1f; 下面我们将讲解Spring对Redis的支持即我们如何在Spring项目中访问Redis。 项目如下 redis.properties 这里指定连接 Redis 服务器的相关信息 #ip地址 redis.host…

基础线性规划实现---python

目录 一、问题 何为线性规划问题&#xff1a; 二、python进行求解 1.通过观察matlab解线性规划步骤进行求解 2.python求解步骤 1&#xff09;求解用到的模块&#xff08;scipy 和 numpy&#xff09;&#xff1a; 2&#xff09;对 max z2x13x2-5x3 该问题确定c如下&…

linux查看mongodb的ip地址,[转载]在MongoDB的shell中显示服务器当前IP

不久前发生一起严重的事故&#xff0c;误删了生产MongoDB服务器上的一个collection&#xff0c;由于没有定时备份&#xff0c;导致几天的数据丢失。认真反省之后&#xff0c;写下这篇文章&#xff0c;希望能给大家一些警示。过程当时我正双开两个终端窗口&#xff0c;分别用mon…

【OpenCV 例程 300篇】240. OpenCV 中的 Shi-Tomas 角点检测

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】240. OpenCV 中的 Shi-Tomas 角点检测 角是直线方向的快速变化。角点通常被定义为两条边的交点&#xff0c;或者说角点的邻域应该具有两个不同区域的不同方向的边界。 角是高度有效的特征。角点检测…

多元统计分析1

第一章 多元正态分布 文章目录 1.1 多元分布的基本概念 1.1.1 随机向量 1.1.2 分布函数与密度函数 联合分布函数&#xff1a; 联合密度函数&#xff1a; 条件密度函数&#xff1a; 分量的独立性&#xff1a; 1.1.3 随机向量的数字特征 1.随机向量的均值 2、随机…

Java并发容器和框架

ConcurrentHashMap 我们为什么要使用 ConcurrentHashMap呢&#xff1f; 原因有三&#xff1a; 并发编程中HashMap会导致死循环&#xff1b;HashTable效率又非常低&#xff1b;ConcurrentHashMap的锁分段技术可有效提升并发访问率。在并发编程使用HashMap会导致死循环。 在多线…