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:原子更新带有版本号的引用类型。

要想原子更新字段,需要两个步骤:

  1. 每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
  2. 更新类的字段(属性)必须为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的更多相关文章

  1. Java并发包concurrent——ConcurrentHashMap

    转: Java并发包concurrent——ConcurrentHashMap 2018年07月19日 20:43:23 Bill_Xiang_ 阅读数 16390更多 所属专栏: Java Conc ...

  2. java并发包分析之———Atomic类型

    一.何谓Atomic?   Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过 ...

  3. 并发包学习之-atomic包

    一,模拟并发代码: 线程不安全的代码 //并发模拟代码 public class CountExample { //请求总数 public static int clientTotal = 5000; ...

  4. Java并发包concurrent类简析

    1.ConcurrentHashMap ConcurrentHashMap是线程安全的HashMap的实现. 1)添加 put(Object key , Object value) Concurren ...

  5. Concurrent初探 --- Atomic 无锁

    一.CAS算法 Compare And Swap,CAS算法的过程是这样:它包含3个参数CAS(V,E,N).V表示要更新的变量,E表示预期值,N表示新值.仅当V值等于E值时,才会将V的值设为N,如果 ...

  6. Java并发包——Atomic操作

    Java并发包——Atomic操作 摘要:本文主要学习了Java并发包下的atomic包中有关原子操作的一些类. 部分内容来自以下博客: https://blog.csdn.net/qq_303796 ...

  7. Java并发包源码分析

    并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善.现代的PC都有多个CPU或一个CPU中有多个 ...

  8. java.util.concurrent包

    在JavaSE5中,JUC(java.util.concurrent)包出现了 在java.util.concurrent包及其子包中,有了很多好玩的新东西: 1.执行器的概念和线程池的实现.Exec ...

  9. 和朱晔一起复习Java并发(四):Atomic

    本节我们来研究下并发包中的Atomic类型. AtomicXXX和XXXAdder以及XXXAccumulator性能测试 先来一把性能测试,对比一下AtomicLong(1.5出来的).LongAd ...

随机推荐

  1. C#定时备份正在播放的幻灯片、word文档、excel电子表格,mht格式文档

    控制台应用, 代码如下: using System; using System.Collections.Generic; using System.IO; using System.Linq; usi ...

  2. 学习任务,阅读一下Redis分布式锁的官方文档

    地址: https://redis.io/topics/distlock 这是一篇质疑RedLock的论文:https://martin.kleppmann.com/2016/02/08/how-to ...

  3. 正确生成浮点型的方法,解决sqlachemy Float浮点型的坑,生成float类型时,长度和精度均为0,导致查询不到结果!

    问题描述 在使用flask_sqlachemy时,给price字段选择了Float类型,数据库用的mysql,生成数据库表后,发现 from sqlalchemy import Float,Colum ...

  4. (predicted == labels).sum().item()作用

    ⚠️(predicted == labels).sum().item()作用,举个小例子介绍: # -*- coding: utf-8 -*-import torch import numpy as ...

  5. Go web编程学习笔记——未完待续

    1. 1).GOPATH设置 先设置自己的GOPATH,可以在本机中运行$PATH进行查看: userdeMacBook-Pro:~ user$ $GOPATH -bash: /Users/user/ ...

  6. Ubuntu16.04环境安装jenkins

    前提:系统必须安装jdk 如果没有安装可通过如下命令安装: sudo add-apt-repository ppa:openjdk-r/ppa sudo apt update sudo apt ins ...

  7. face alignment[Ordinary Procrustes Analysis]

    人脸识别,大致可以分为以下四个步骤: 人脸检测:从图片中准确定位到人脸,并以矩形框将其裁剪出来: 人脸矫正(对齐): 检测到的人脸,可能角度不是很正,需要使其对齐,比如旋转,缩放: 特征提取:对矫正后 ...

  8. BZOJ3561 DZY Loves Math VI 莫比乌斯反演

    传送门 看到\(gcd\)相关先推式子(默认\(N \leq M\)): \(\begin{align*} \sum\limits_{i=1}^N \sum\limits_{j=1}^M (lcm(i ...

  9. 记一次Dubbo服务注册异常

            公司项目重构,把dubbo版本从2.5.8升级为2.6.2.升级后在本地运行一点问题都没有:可是通过公司自研的发布系统将项目发布到测试环境的linux服务器下面后,出现了dubbo服务 ...

  10. [Spark][Python][DataFrame][RDD]DataFrame中抽取RDD例子

    [Spark][Python][DataFrame][RDD]DataFrame中抽取RDD例子 sqlContext = HiveContext(sc) peopleDF = sqlContext. ...