Java并发 - (无锁)篇6
, 摘录自葛一鸣与郭超的 [Java高并发程序设计].
本文主要介绍了死锁的概念与一些相关的基础类, 摘录自葛一鸣与郭超的 [Java高并发程序设计].
无锁是一种乐观的策略, 它假设对资源的访问是没有冲突的, 所有的线程都可以不停顿地持续执行, 它采用一种叫做比较交换的技术 (CAS Compare And Swap) 来鉴别线程冲突, 如果出现冲突, 重试操作, 直到没有冲突为止.
CAS
CAS算法包含三个参数 CAS(V, E, N). V表示要更新的变量, E表示预期值, N表示新值.
如果 V==E 成立, 就将 V 设置为 N, 如果 V 于 E 值不同, 说明有别的线程对 V 进行了更新, 则当前线程什么都不做. CAS 的操作总是抱着乐观的态度进行的, 它总认为自己可以成功完成操作. 多个线程使用 CAS 操作一个变量时, 只有一个会胜出, 其他的被告知失败, 并允许再次尝试或者放弃.
典型的 CAS 算法包括
- 一个死循环来控制的连续的尝试
- 一次获取 V 保存到一个本地变量 E 的操作
- 一个对本地变量进行处理得到 N 的方法
- 一次对目前的 V 和之前得到的 E 的值的比较
- 一次根据真假来确定是否要将 N 覆盖当前 V 的的操作
- 对应的尝试失败的处理方法
例如, 这是一个下文中的一段涉及到CAS的代码的示例
1 |
new Thread() { for(;;){ |
正是由于这种机制 (额外给出一个期望值来判断操作是否成功), 所以即使CAS操作没有锁, 也可以发现别的线程的干扰, 并进行恰当的处理.
在硬件层面, 大部分的现代化处理器都已经支持原子化的 CAS 指令, 虚拟机可以使用这个指令来实现操作与数据结构
无锁的线程安全整数: AtomicInteger
这个类是一个可变的, 线程安全的整数, 也就是一个原子类, 这里展示它的一些主要方法
1 |
public final int () |
其他的原子类的操作也非常类似.
AtomicInteger的核心字段是
1 |
private volatile int value; |
incrementAndGet 中 CAS 的实现 JDK 1.7
1 |
public final int incrementAndGet() { |
incrementAndGet 中 CAS 的实现 JDK 1.8
1 |
public final int incrementAndGet() { |
代码中使用到了sun.misc.Unsafe
类, JKD1.7 的对应实现中使用的compareAndSet()
方法也来自于Unsafe
类, 这个类封装了一些类似指针的操作, 例如 “比较并设置int值” 方法
1 |
// Object o, long offeset, int expected, int x |
其中第一个为给定的对象, 第二个为一个字段到对象头部的偏移量, 通过它来快速定位字段, 第三个为期望值, 第四个为要设置的值,
无法由程序员自己实现
实际上, Unsafe
类中类似指针的操作不能被我们自己的应用程序使用, 为了获得Unsafe
类, 我们就需要调用其工厂方法, 而它做了相关的检测来静止用户调用.
1 |
public static Unsafe getUnsafe() { |
无锁的对象: AtomicReference
一个泛型类, 利用这个类可以实现普通对象的原子性, 一般使用它三种基本的方法即可, 与我们自己去实现的不同, 它的方法都保证了原子性.
1 |
public final V () |
通过这三种方法, 我们可以完成一些更具体的 CAS 操作
例如, 下面使得一个值小于 20 时对其加 20
1 |
static AtomicReference<Integer> money = new AtomicReference<>(); |
这里启动了三个线程, 并且采用了 CAS 策略 . 明显的是, 只有一个线程能成功地更改值, 其他两个线程会失败, ,这里简单地取消处理, 我们也可以让这两个线程继续尝试, 直到别的线程修改了 money 的值, 使其低于20.
带时间戳的无锁对象: AtomicStampedReference
假设有这样的一种情况, 我们维护一组用户的账户余额数据, 现在进行一次刺激充值的活动, 所有余额低于20元的用户都能收到20元余额, 为了加速操作, 我们启动了若干个后台线程, 持续扫描用户数据, 用来完成足够快的增值服务响应.
存在这样一种异常情况, 一个账户的余额是15元, 两个线程同时get()
到了目前的余额, 发现它需要被充值, 一个线程优先完成了充值操作, 余额变为35元. 而在另外一个线程检测到余额已被更改前, 用户消费了20元, 余额又变回了15元, 第二个线程又将余额更改为35元. 这样就给用户累积充值了40元, 虽然发生的概率非常小, 但是仍然可能出现
上述情况的原因是因为AtomicRefernce
类将对象的值与本身状态划上了等号, 这在一些涉及到时间戳的应用中是不符合现实模型的. AtomicStampedReference
在内部不仅维护了对象值还维护了一个时间戳 (final int stamp
), 只有当对象值与时间戳都满足期望值时, 才会写入成功.
这里列出几个主要的 API
1 |
public V getReference() |
每次对对象的更改都需要对时间戳进行更改, 下面提供一个示例
1 |
static AtomicStampedReference<Integer> money = new AtomicStampedReference<>(); |
无锁的数组: Atomic??Array
JDK 的Unsafe
类通过 CAS 的方式也为我们实现了一些无锁的数组, 它们的名字都是 Atomic??Array
的格式.
这里以AtomicIntegerArray
为例, 展示原子数组的使用
AtomicIntegerArray
实际上是利用Unsafe
的 CAS 方式来完成对int[]
的封装. 下面是它的核心 API
1 |
public final int (int i) |
让普通变量也能享受到原子操作: Atomic??FieldUpdater 接口
有时候, 由于初期考虑不周或者需求变化, 一些普通变量也会存在需要线程安全的需求. 这时候, 我们可以简单地利用synchronized
或者ReentrantLock
来解决, 但是如果需要用到这个变量的地方很多, 这样的作法明显违反了开闭原则 (系统对功能的增加应该是开放的, 但对修改应该是保守的).
事实上 JDK 在原子包中为我们提供了相关的实用类, 来使得只要修改极少的代码就能获得线程安全的保证. 这更像是一种高效的补救的措施.
根据数据类型Updater
类有三种选择
AtomicReferenceFieldUpdater
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
为了使用这个接口, 我们需要调用它的newUpdatar()
方法, 这里以AtomicIntegerFieldUpdater
为例
1 |
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) |
与AtomicInteger
一样, 它也提供了incrementAndGet()
这种更新方法, 详细资料请自己查阅JDK文档.
注意事项
- 他们都是基于反射 而制作的工具类! 只能修改它可见的范围内的变量.
- 修改的变量必须为
volatile
类型. - 由于使用了
Unsafe
类, 利用偏移量修改值, 所以不支持静态类型的修改.
Java并发 - (无锁)篇6的更多相关文章
- 【JAVA并发第四篇】线程安全
1.线程安全 多个线程对同一个共享变量进行读写操作时可能产生不可预见的结果,这就是线程安全问题. 线程安全的核心点就是共享变量,只有在共享变量的情况下才会有线程安全问题.这里说的共享变量,是指多个线程 ...
- Java并发-同步容器篇
作者:汤圆 个人博客:javalover.cc 前言 官人们好啊,我是汤圆,今天给大家带来的是<Java并发-同步容器篇>,希望有所帮助,谢谢 文章如果有问题,欢迎大家批评指正,在此谢过啦 ...
- java 并发多线程 锁的分类概念介绍 多线程下篇(二)
接下来对锁的概念再次进行深入的介绍 之前反复的提到锁,通常的理解就是,锁---互斥---同步---阻塞 其实这是常用的独占锁(排它锁)的概念,也是一种简单粗暴的解决方案 抗战电影中,经常出现为了阻止日 ...
- JAVA 中无锁的线程安全整数 AtomicInteger介绍和使用
Java 中无锁的线程安全整数 AtomicInteger,一个提供原子操作的Integer的类.在Java语言中,++i和i++操作并不是线程安全的,在使用的时候, 不可避免的会用到synchron ...
- 从源码学习Java并发的锁是怎么维护内部线程队列的
从源码学习Java并发的锁是怎么维护内部线程队列的 在上一篇文章中,凯哥对同步组件基础框架- AbstractQueuedSynchronizer(AQS)做了大概的介绍.我们知道AQS能够通过内置的 ...
- Java并发编程锁之独占公平锁与非公平锁比较
Java并发编程锁之独占公平锁与非公平锁比较 公平锁和非公平锁理解: 在上一篇文章中,我们知道了非公平锁.其实Java中还存在着公平锁呢.公平二字怎么理解呢?和我们现实理解是一样的.大家去排队本着先来 ...
- Java并发编程锁系列之ReentrantLock对象总结
Java并发编程锁系列之ReentrantLock对象总结 在Java并发编程中,根据不同维度来区分锁的话,锁可以分为十五种.ReentranckLock就是其中的多个分类. 本文主要内容:重入锁理解 ...
- java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock
原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...
- Java高并发-无锁
一.无锁类的原理 1.1 CAS CAS算法的过程是这样:它包含3个参数CAS(V,E,N).V表示要更新的变量,E表示预期值,N表示新值.仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同, ...
随机推荐
- LeetCode Input Initial Code
说明 LeetCode提供的样本输入,显示上是数组Array,而后台的实际测试用例则是树TreeNode,链表ListNode等. 如果你是在页面手撸代码直接提交的,那没什么影响. 如果你是在本地ID ...
- [原]排错实战——通过对比分析sysinternals事件修复程序功能异常
原调试debug排错troubleshootprocess monitorsysinternals 缘起 最近,我们程序的某个功能在一台机器上不正常,但是在另外一台机器上却是正常的.代码是同一份,vs ...
- 02-python-运算符与表达式
目录 1. 比较运算符 2. 算数运算符 3. 赋值运算符 4. 位于运算符 5. 逻辑运算符 6. 成员运算符 7. 身份运算符 8. 运算符优先级 9. 输出输入 10. 数字类型转换及常用数学方 ...
- 后台用Hbase对表单数据实现增删改查遇到的问题
1.无法解析jsp 原因:hbase中lib下jar包会与tomcat包冲突,需要删除与tomcat冲突的包 这是我删除的几个包 之后运行就没有问题了 2.对于Hbase修改的问题 在添加数据时,HB ...
- Java的各类型数据在内存中分配情况详解
1. 有这样一种说法,如今争锋于IT战场的两大势力,MS一族偏重于底层实现,Java一族偏重于系统架构.说法根据无从考证,但从两大势力各自的社区力量和图书市场已有佳作不难看出,此说法不虚,但 ...
- 内网部署Docker版本Gitlab
Gitlab部署: 1. 还原备份文件后记得拷贝gitlab-secrets.json,不然会遇到500错误 2. 下载Docker以及依赖项rpm包 3. 在外网机器下载镜像 a. 拉取——Dock ...
- linux c 调用 so 库
/***********编译时要链接 -l dl 库************/ #include<stdlib.h> #include<stdio.h> #include< ...
- 小程序中map的取值和赋值
1.初始化 resultMap: { "near": [], "join": [], "publish": [] } 2.js中直接取 co ...
- Hardy-Weinberg laws
I.3 Diploids with two alleles: Hardy-Weinberg laws 假设子代是Aa,AA,aa的概率分别是PAa,PAA,Paa,A的基因概率是P1,a的基因概率是P ...
- 十八、linux系统分区
一.磁盘存储结构图:这里注意下,分区标有64字节,则每个分区有16字节,MBR引导分区有446字节,共有510字节,一个扇区有512字节,还有俩个字节是分区结束标识.比如隐藏文件等标识,都是这2个字节 ...