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值不同, ...
随机推荐
- Python重学记录1
写下这个标题觉得可笑,其实本人2014年就自学过一次python,当时看的是中谷教育的milo老师的视频,也跟着写了一些代码,只是因为当时工作上用不到也就淡忘了.不过说实话当时的水平也很低下,本来也没 ...
- UML-SSD-定义
1.NextGen例子 SSD来自用例文本 2.定义 1).针对的是用例的一个特定场景 2).参与者与系统之间交互事件(跨系统边界,不画系统内部流转,即黑盒) 比如:收银员 访问系统A.系统B,此时只 ...
- Java利用DES/3DES/AES这三种算法分别实现对称加密
转载地址:http://blog.csdn.net/smartbetter/article/details/54017759 有两句话是这么说的: 1)算法和数据结构就是编程的一个重要部分,你若失掉了 ...
- 八、Shell脚本高级编程实战第八部
一.使用for循环在/oldboy目录下创建10个文件名为oldboy-x的文件 #!/bin/sh[ ! -d /oldboy ] && mkdir -p /oldbfor i in ...
- 17.3.13---sys.argv[]用法
1------sys.argv[]是用来获取命令行参数, sys.argv[0]表示代码本身文件路径,因此要从第二个即sys.argv[1]开始去参数 例如创建一个文件: import sys pri ...
- 14 微服务电商【黑马乐优商城】:day03-springcloud(Zuul网关)
本项目的笔记和资料的Download,请点击这一句话自行获取. day01-springboot(理论篇) :day01-springboot(实践篇) day02-springcloud(理论篇一) ...
- linux下tab作用的描述?
[Tab] 接在一串指令的第一个字的后面,则为命令补全; 实例怎么描述?什么叫一串指令的第一个字?[Tab] 接在一串指令的第二个字以后时,则为『文件补齐』 实例怎么描述?什么叫一串指令的 ...
- ubuntu19.10——snap错误has install-snap change in progress
使用软件商店安装时遇到问题 snap has install-snap change in progress 原因是之前的安装错误终止,使得现在的安装无法进行,解决方案: 终端输入: snap cha ...
- Java复习(二)类与对象的基本概念
2.1面向对象的程序设计方法概述 对象 程序中: 一切皆是对象 都具有标识,属性和行为 通过一个或多个变量来保存其状态 通过方法实现他的行为 类 将属性及行为相同或相似的对象归为一类 类可以看成是对象 ...
- 添加新硬盘,扩展Centos7根分区
##背景介绍,系统安装时,分配的硬盘容量太小,根分区空间不够用,现添加一个新硬盘,通过以下步骤来扩展centos7根分区 [root@t201 ~]# df -h 文件系统 容量 已用 可用 已用% ...