原子性的定义:
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。简单点就是:要么这个操作不进行,要么就进行到底,就不用担心线程切换问题
count++不是一个原子性操作,他在执行的过程当中有可能被其他线程打断。
count++实际上在底层是:
tamp=count+1;
count=tamp;
然后再:
- 从共享数据中读取数据到本线程栈中
- 修改本线程栈中变量副本的值,
- 把本线程中变量副本的值赋值给共享数据
这其中的每一步都有可能在执行的过程中被别的线程打断,多以count++不是一个
原子性操作
volatile关键字也不能保证原子性:
- volatile关键字只能保证线程每次读取的值都是最新的值(都是共享数据本身的值)
- 但是不能保证原子性
加锁可以保证操作的原子性
原子类 AtomicInteger
概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解
使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:
- AtomicBoolean: 原子更新布尔类型
- AtomicInteger: 原子更新整型
- AtomicLong:
以上3个类提供的方法几乎一模一样,所以本节仅以AtomicInteger为例进行讲解,AtomicInteger的常用方法如下:
- public AtomicInteger(): 初始化一个默认值为0的原子型Integer
- public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
- int get(): 获取值
- int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
- int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
- int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
- int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
代码实现:
1 package com.itheima.threadatom3; 2 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 public class MyAtomIntergerDemo1 { 6 // public AtomicInteger(): 初始化一个默认值为0的原子型Integer 7 // public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer 8 public static void main(String[] args) { 9 AtomicInteger ac = new AtomicInteger(); 10 System.out.println(ac); 11 12 AtomicInteger ac2 = new AtomicInteger(10); 13 System.out.println(ac2); 14 } 15 16 }
1 package com.itheima.threadatom3; 2 3 import java.lang.reflect.Field; 4 import java.util.concurrent.atomic.AtomicInteger; 5 6 public class MyAtomIntergerDemo2 { 7 // int get(): 获取值 8 // int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。 9 // int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。 10 // int addAndGet(int data): 以原子方式将参数与对象中的值相加,并返回结果。 11 // int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。 12 public static void main(String[] args) { 13 // AtomicInteger ac1 = new AtomicInteger(10); 14 // System.out.println(ac1.get()); 15 16 // AtomicInteger ac2 = new AtomicInteger(10); 17 // int andIncrement = ac2.getAndIncrement(); 18 // System.out.println(andIncrement); 19 // System.out.println(ac2.get()); 20 21 // AtomicInteger ac3 = new AtomicInteger(10); 22 // int i = ac3.incrementAndGet(); 23 // System.out.println(i);//自增后的值 24 // System.out.println(ac3.get()); 25 26 // AtomicInteger ac4 = new AtomicInteger(10); 27 // int i = ac4.addAndGet(20); 28 // System.out.println(i); 29 // System.out.println(ac4.get()); 30 31 AtomicInteger ac5 = new AtomicInteger(100); 32 int andSet = ac5.getAndSet(20); 33 System.out.println(andSet); 34 System.out.println(ac5.get()); 35 } 36 }
Atomic的内存解析:
Atomic原理:自旋锁+CAS算法
CAS算法:
先给一段比较晦涩的概念:
- 有3个操作数(内存值V, 旧的预期值A,要修改的值B)
- 当旧的预期值A == 内存值 此时修改成功,将V改为B
- 当旧的预期值A!=内存值 此时修改失败,不做任何操作
- 并重新获取现在的最新值(这个重新获取的动作就是自旋)
我们图解解释什么是CAS算法和自旋:
现在假设有AB两个线程使用内存中的共性数据100,如图所示:
当AB完成初始化取值时:
此时A执行加一操作(Atomic),则原来的副本100就会变成旧值,新值(要修改的值)是101,101要赋值给副本,这个时候比较一下旧值和内存中共享数据值是否相等。
如果相等则把新值赋值给内存中共享数据,如果不等(不等就表明被别的线程操作过这里假设B线程操作过)则把内存中的值重新赋值给赋值给副本,自增,原值变旧值,然后旧值再与内存中的共享数据比较相等则把新值赋值给共享,不等重复以上操作。。。如下图:
重复比较这一步称作为自旋