并发包 concurrent(一) Atomic
1:基础概念
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
原子操作:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程),是不需要synchronized,通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。
例如下面的例子 IncrementAndGet类中的next()方法是一个自增1操作,为了保证线程安全加了synchronized关键字
- public class IncrementAndGet() {
- private int value;
- public synchronized int next(){
- return value++;
- }
- }
这种加锁的方式属于悲观锁的方式,效率太低。可以采用例外一种方式处理:
1. 从内存中读取value 值,假设为10, 我们把这个值称为A
2. B = A+1 得到 B = 11
3. 用A 和 内存的值相比, 如果相等(就是说在过去的一段时间,没人修改内存的值), 那就把B的值(11)写入内存, 如果不相等(就是说过去的一段时间, 有人修改了内存value 的值), 意味着A已经不是最新值了, 那就放弃这次修改, 跳回第1步去”
第三步其实就是一条硬件指令,保证原子执行。 在单个CPU上就不用说了,如果是有多个CPU, 这个指令甚至会锁住总线, 确保同一时刻只有一个CPU能访问内存!
- final AtomicInteger value = new AtomicInteger(10);
- @Test
- public final int test1(){
- for(;;){
- int current = value.get();//获取当前值
- int next = current+1; //设置期望值
- if(value.compareAndSet(current, next)){
- return next;
- }
- }
- }
AtomicInteger类compareAndSet通过原子操作实现了CAS操作,最底层基于汇编语言实现。
CAS是Compare And Set的一个简称,如下理解:
1,已知当前内存里面的值current和预期要修改成的值new传入
2,内存中AtomicInteger对象地址对应的真实值(因为有可能别修改)real与current对比,
相等表示real未被修改过,是“安全”的,将new赋给real结束然后返回;不相等说明real已经被修改,结束并重新执行1直到修改成功
我们仔细地审视这段代码, 它根本没有加锁, 其他线程都可以进入next()方法, 读取数据,操作数据, 最后使用CAS来决定这次操作是否有效, 如果内存值被别人改过,那就再次循环尝试,这就采用了乐观锁的方式。
为了说明AtomicInteger的原子性,这里代码演示多线程对一个int值进行自增操作,最后输出结果,代码如下:
- public class AtomicIntegerDemo {
- private static AtomicInteger atomicInteger = new AtomicInteger(0);
- public static void main(String[] args){
- for (int i = 0; i < 5; i++){
- new Thread(new Runnable() {
- public void run() {
- //调用AtomicInteger的getAndIncement返回的是增加之前的值
- System.out.println(atomicInteger.getAndIncrement());
- }
- }).start();
- }
- System.out.println(atomicInteger.get());
- }
- }
输出结果如下:
- 0
- 2
- 1
- 3
- 4
- 4
原子更新数组
通过原子更新数组里的某个元素,共有3个类:
- AtomicIntegerArray:原子更新整型数组的某个元素
- AtomicLongArray:原子更新长整型数组的某个元素
- AtomicReferenceArray:原子更新引用类型数组的某个元素
AtomicIntegerArray常用的方法有:
- int getAndSet(int i, int delta):以原子方式将输入值与数组中索引为i的元素相加
- boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式更新数组中索引为i的值为update值
- public class AtomicIntegerArrayDemo {
- static int[] value = new int[]{1, 2};
- static AtomicIntegerArray ai = new AtomicIntegerArray(value);
- public static void main(String[] args){
- ai.getAndSet(0,3);
- System.out.println(ai.get(0));
- System.out.println(value[0]);
- }
运行结果是:
3
1
数组value通过构造的方式传入AtomicIntegerArray中,实际上AtomicIntegerArray会将当前数组拷贝一份,所以在数组拷贝的操作不影响原数组的值。
原子更新引用类型
需要更新引用类型往往涉及多个变量,早atomic包有三个类:
- AtomicReference:原子更新引用类型
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicMarkableReference:原子更新带有标记位的引用类型。
下面以AtomicReference为例进行说明:
- public class AtomicReferenceDemo {
- static class User{
- private String name;
- private int id;
- public User(String name, int id) {
- this.name = name;
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- }
- public static AtomicReference<User> ar = new AtomicReference<User>();
- public static void main(String[] args){
- User user = new User("aa",11);
- ar.set(user);
- User newUser = new User("bb",22);
- ar.compareAndSet(user,newUser);
- System.out.println(ar.get().getName());
- System.out.println(ar.get().getId());
- }
- }
运行结果为:
bb
22
可以看到user被成功更新。
原子更新字段类
如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:
- AtomicIntegerFieldUpdater:原子更新整型字段
- AtomicLongFieldUpdater:原子更新长整型字段
- AtomicStampedReference:原子更新带有版本号的引用类型。
要想原子更新字段,需要两个步骤:
- 每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
- 更新类的字段(属性)必须为public volatile
下面的代码演示如何使用原子更新字段类更新字段:
- public class AtomicIntegerFieldUpdaterDemo {
- //创建一个原子更新器
- private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
- AtomicIntegerFieldUpdater.newUpdater(User.class,"old");
- public static void main(String[] args){
- User user = new User("Tom",15);
- //原来的年龄
- System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
- //现在的年龄
- System.out.println(atomicIntegerFieldUpdater.get(user));
- }
- static class User{
- private String name;
- public volatile int old;
- public User(String name, int old) {
- this.name = name;
- this.old = old;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getOld() {
- return old;
- }
- public void setOld(int old) {
- this.old = old;
- }
- }
- }
输出的结果如下:
15
16
并发包 concurrent(一) Atomic的更多相关文章
- Java并发包concurrent——ConcurrentHashMap
转: Java并发包concurrent——ConcurrentHashMap 2018年07月19日 20:43:23 Bill_Xiang_ 阅读数 16390更多 所属专栏: Java Conc ...
- java并发包分析之———Atomic类型
一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过 ...
- 并发包学习之-atomic包
一,模拟并发代码: 线程不安全的代码 //并发模拟代码 public class CountExample { //请求总数 public static int clientTotal = 5000; ...
- Java并发包concurrent类简析
1.ConcurrentHashMap ConcurrentHashMap是线程安全的HashMap的实现. 1)添加 put(Object key , Object value) Concurren ...
- Concurrent初探 --- Atomic 无锁
一.CAS算法 Compare And Swap,CAS算法的过程是这样:它包含3个参数CAS(V,E,N).V表示要更新的变量,E表示预期值,N表示新值.仅当V值等于E值时,才会将V的值设为N,如果 ...
- Java并发包——Atomic操作
Java并发包——Atomic操作 摘要:本文主要学习了Java并发包下的atomic包中有关原子操作的一些类. 部分内容来自以下博客: https://blog.csdn.net/qq_303796 ...
- Java并发包源码分析
并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善.现代的PC都有多个CPU或一个CPU中有多个 ...
- java.util.concurrent包
在JavaSE5中,JUC(java.util.concurrent)包出现了 在java.util.concurrent包及其子包中,有了很多好玩的新东西: 1.执行器的概念和线程池的实现.Exec ...
- 和朱晔一起复习Java并发(四):Atomic
本节我们来研究下并发包中的Atomic类型. AtomicXXX和XXXAdder以及XXXAccumulator性能测试 先来一把性能测试,对比一下AtomicLong(1.5出来的).LongAd ...
随机推荐
- 【teradata】强制解锁
使用加锁用户释放锁,也可以用其它用户使用如下语句强制解锁 Release lock (pdm_data ),Override
- input reset 重置时间
经验规律,301毫秒. function autoFormatMoney() { if (!this.value.length) {return} var num = parseFloat(this. ...
- Python学习之装饰器进阶
函数知识回顾: 函数的参数分为:实参和形参. 实参:调用函数的时候传入的参数: 形参:分为3种(位置参数.默认参数.动态传参) 位置参数:必须传值 def aaa(a,b): print(a,b) a ...
- 解决y7000笔记本ubuntu18.04下 休眠挂起后唤醒花屏
定位问题,切换到核显后发现一点问题也没有,基本确定是显卡驱动的问题 但是由于配置环境比较复杂,不想重新装N卡驱动,所以另寻方法 sudo gedit /etc/default/grub 修改前 # I ...
- Deep Knowledge Tracing (深度知识追踪)
论文:Deep Knowledge Tracing Addressing Two Problems in Deep Knowledge Tracing via Prediction-Consis ...
- 90道Python面试题,做对80%直击年薪40w
转发链接: 1.一行代码实现1--100之和 利用sum()函数求和 2.如何在一个函数内部修改全局变量 函数内部global声明 修改全局变量 3.列出5个python标准库 os:提供了不少与操作 ...
- 洛谷题解 P1138 【第k小整数】
蒟蒻发题解了 说明:此题我用的方法为桶排(我翻了翻有人用了桶排只不过很难看出来,可能有些重复的,这个题只是作为一个专门的桶排来讲解吧) (不会算抄袭吧 ‘QWaWQ’) 简单来说(会的人跳过就行): ...
- 【mongoDB查询进阶】聚合管道(三)--表达式操作符
https://segmentfault.com/a/1190000010910985 管道操作符的分类 管道操作符可以分为三类: 阶段操作符(Stage Operators) 表达式操作符(Expr ...
- Redis学习之字典源码分析
字典,又叫映射,是一种用于保存键值对的抽象数据结构 划重点:抽象数据结构 Redisd字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表结点,而每个哈希表结点就保存了字典中的一个键值对 一.哈 ...
- WebBench压力测试工具(详细源码注释+分析)
本文适合人群:对WebBench实现感兴趣的人 WebBench原理: Linux下使用的服务器压力测试工具,利用fork建立多个子进程,每个子进程在测试时间内不断发送请求报文,建立多个连接,然后由父 ...