多线程之原子类(八)
Java并发包中主要基于两个基础来构建的,一个是锁,一个是CAS操作。
- 原子变量提供了与volatile类型变量相同的内存语义,此外还支持原子性操作。从JDK1.5开始,提供了
java.util.concurrent.atomic
包,这个包中的原子操作提供了一种用法简单,性能高效,线程安全的更新一个变量的方式。原子类采用非阻塞算法CAS实现 - 非阻塞算法可以使多个线程在竞争相同的数据时不会发生阻塞。独占锁可以看做是一种悲观锁,它假设只要有线程进入就会导致错误,因此只在确保无其它线程进入的时候才进行操作;非阻塞算法,则只关心结果,如果结果错误了,那么重新再来,对于错误选择原谅,而不是想进办法防止其它线程进入,使用非阻塞算法无需关心其它线程。
Java中对非阻塞算法的支持是java.util.concurrent.atomic
包中的原子类。
原子类划分
基本类型:AtomicBoolean,AtomicInteger,AtomicLong
数组: AtomicIntegerArray,AtomicLongArray,AtomicRefernceArray
引用类型:AtomicReference, AtomicReferenceFieldUpdater,AtomicMarkableReference
字段类:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicStampedReference
CAS算法
原子类的操作本质上都是CAS算法的实现。
CAS算法过程:
CAS包含了3个操作数,需要读写的内存位置V,进行比较的值E(exists),和要写入的值N(new)。
当且仅当V的值等于E的值的时候,才会把V的值设置成N。最后CAS返回V的真实值。
原子类案例
原子类是一种更好的volatile变量,之前我们保证同步的措施是使用加锁的机制,无需加锁,也可以做到。
原子类的核心API实现
基本上所有原子类都有这些API,它们的操作也都是利用这些API实现的。而这些API又都是利用Unsafe类来实现的。
如果我们直接实例化Unsafe类,系统会报SecurityException
异常,平时开发中也不建议使用Unsafe类来做各种操作,但是有关Unsafe类的一些用法可以了解下。
Unsafe类可以直接操作内存层面上的数据。
compareAndSet(V,E,N) //在对象var1变量上,var2为地址,var4期望的值,var5位要设置的新值。 public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
原子类基本类型案例
// 计数器案例 public class IntegerDemo implements Runnable{ static AtomicInteger count = new AtomicInteger(0); static int THREAD_COUNT = 100; @Override public void run() { for (int i = 0; i < 100; i++) { // CAS自增语句 count.incrementAndGet(); } } public static void main(String[] args) throws InterruptedException { IntegerDemo demo = new IntegerDemo(); Thread[] threads = new Thread[THREAD_COUNT]; for (int i = 0; i < THREAD_COUNT; i++) { threads[i] = new Thread(demo); threads[i].start(); } for (int i = 0; i < THREAD_COUNT; i++) { threads[i].join(); } System.out.println(count.get()); } } //可以看到每次结果都是: 10000
这段代码中重要的是原子类的incrementAndGet方法,看一下内部实现。
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } 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; } public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
原子引用类型案例
关于AtomicRefernce的使用
public class ReferenceDemo { private static AtomicReference<Integer> count = new AtomicReference<>(0); public static void main(String[] args) { count.compareAndSet(0, 2); // 2 count.compareAndSet(0, 1); // no count.compareAndSet(1, 3); // no count.compareAndSet(2, 4); // 4 count.compareAndSet(3, 5); // no System.out.println(count.get()); } } //运行结果得4
//通过CAS算法,修改对象成员变量的值 // 要求成员变量是非静态类型变量最好是volatile修饰的 public class ReferenceFiledUpdaterDemo { private static AtomicIntegerFieldUpdater<ReferenceFiledUpdaterDemo> updater = AtomicIntegerFieldUpdater.newUpdater(ReferenceFiledUpdaterDemo.class, "count"); @Getter public volatile int count = 100; public static void main(String[] args) { ReferenceFiledUpdaterDemo demo = new ReferenceFiledUpdaterDemo(); if (updater.compareAndSet(demo, 100, 120)) { System.out.println(("update success 1 :" + demo.getCount())); } if (updater.compareAndSet(demo, 100, 120)) { System.out.println(("update success 2 :" + demo.getCount())); } else { System.out.println(("update failed :" + demo.getCount())); } } } //运行结果 update success :120 update failed, {} :120
AtomicReference无法解决ABA问题,所谓ABA问题,对象在某一段时间内被写入了两次,首先修改为其它的值,然后又修改回原来的值,而另外一个线程再去读的时候值并没有变化,如何知道对象是否被修改过呢?
JDK中引入了AtomicStampedReference,它维护了一个时间戳,更新数据的时候还要更新时间戳,当对象值,及时间戳都满足期望值的时候才能写入成功。
原子类数组案例
AtomicArray的核心API
public class AtomicArrayDemo { static AtomicIntegerArray arr = new AtomicIntegerArray(10); public static class SubThread implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { arr.getAndDecrement( i % arr.length() ); } } } public static void main(String[] args) { Runnable demo = new SubThread(); ExecutorService service = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { service.submit(demo); } service.shutdown(); System.out.println(arr); } } //运行结果 [-1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000]
三种原子性机制对比
最后
这次主要对原子类相关的内容做了简单的说明,需要明白原子类的用法以及Unsafe类的一些事项。
参考
- Java Magic. Part 4: sun.misc.Unsafe
- 《Java并发编程实战》
- 《Java高并发程序设计》

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
常见自动化测试工具,你用过哪些?
一 Appium 官网:http://appium.io AppUI自动化测试 Appium 是一个移动端自动化测试开源工具,支持iOS 和Android 平台,支持Python、Java 等语言,即同一套Java 或Python 脚本可以同时运行在iOS 和Android平台,Appium 是一个C/S 架构,核心是一个 Web 服务器,它提供了一套 REST 的接口。当收到客户端的连接后,就会监听到命令,然后在移动设备上执行这些命令,最后将执行结果放在 HTTP 响应中返还给客户端。 二Selenium 官网:https://www.seleniumhq.org/download/ WebUI自动化测试 Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7、8、9)、Mozilla Firefox、Mozilla Suite等。这个工具的主要功能包括:测试与浏览器的兼容性——测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。测试系统功能——创建回归...
- 下一篇
漫谈计算机语言
面对需求人员不断提出的变态需求,软件开发人员的主要工作就是将需求人员口中或文档中的自然语言翻译成计算机能够理解的形式语言。自然语言指的是人类的语言,比如汉语、英语等,它具有多义性(在不同的上下文中,意义是不同的),冗余性(语法错了一点 并不会让人引起误解)。而形式语言是用精确的数学或机器可处理的公式定义的语言,例如计算机编程语言,它的特点就是语法非常严格,并不具有多义性和冗余性。 计算机编程语言可以分为高级语言和低级语言。高级语言编程更加容易、维护容易,具备可移植性(一份代码可以运行在多个平台上),典型代表 :Java、Python、C、C++、Ruby、C#等。低级语言只能在某个平台上运行,不具备可移植性,比如说我们在Intel x86编写的代码,无法在PowerPC或ARM上运行,它的典型代表有机器语言和汇编语言。 高级语言中又可以分为解释型语言(interpreting)和编译型语言(compiling)。解释型语言通过解释器读入高级语言写的指令,然后解释执行,Ruby、Python、PHP都是解释型语言。编译型语言使用编译器把高级语言变成目标代码。下面举个例子来介绍解释型语言和...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS6,CentOS7官方镜像安装Oracle11G
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8编译安装MySQL8.0.19