原子性:操作过程中不允许其他线程干扰, 可以理解为数据操作是整体,整体只有成功或失败,不允许出现部分成功部分失败。
(1)AtomicInteger原子类的使用
- public class Test2 {
- public static void main(String[] args) {
- //使用int原子类, 初始值设置为0
- AtomicInteger atomicInteger = new AtomicInteger(0);
- //创建线程池
- ExecutorService executorService = Executors.newFixedThreadPool(5);
- //创建五个任务
- for (int i = 0; i < 5; i++) {
- //五个线程处理任务
- executorService.submit(new Runnable() {
- @Override
- public void run() {
- for (int i1 = 0; i1 < 10000; i1++) {
- //先+1再获取
- int i2 = atomicInteger.incrementAndGet();
- System.out.println(Thread.currentThread().getName()+": "+i2);
- }
- }
- });
- }
- try {
- Thread.sleep(2000);
- System.out.println("总和:"+atomicInteger.get());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
(2)AtomicInteger方法介绍
- package com.java.test;
-
- import java.util.concurrent.atomic.AtomicInteger;
-
- public class Test01 {
- public static void main(String[] args) {
- AtomicInteger atomicInteger = new AtomicInteger();//0
-
- //先获取,后加1
- int andIncrement = atomicInteger.getAndIncrement();//a++
- System.out.println(andIncrement);//0
-
- //先加1 后获取
- int i = atomicInteger.incrementAndGet();//++a
- System.out.println(i);//2
-
- //先获取,后减1
- int andDecrement = atomicInteger.getAndDecrement();//a--
- System.out.println(andDecrement);//2
-
- //先减1 后获取
- int i1 = atomicInteger.decrementAndGet();//--a
- System.out.println(i1);//0
-
- //先获取 后加10
- int andAdd = atomicInteger.getAndAdd(10);//a += 10
- System.out.println(andAdd);//0
-
- //先加5 后获取
- int i2 = atomicInteger.addAndGet(5);//a += 5
- System.out.println(i2);//15
-
- //先获取 后设置新值
- int andSet = atomicInteger.getAndSet(50);//a = 50
- System.out.println(andSet);//15
-
- //设置新值
- atomicInteger.set(25);// a = 25
-
- //获取值
- int i3 = atomicInteger.get();
- System.out.println(i3);//25
- }
- }
在多线程环境下,volatile 关键字可以保证共享数据的可见性,有序性, 但是并不能保证对数据操作的原子性(数据操作是整体,整体只有成功或失败,不允许出现部分成功部分失败)。也就是说,多线程环境下,使用 volatile 修饰的变量是线程不安全的。
概念:内存可见性是指当一个线程修改了某个变量的值,其它线程总是能立即知道这个变量的变化。也就是说,如果线程 A 修改了共享变量 V 的值,那么线程再B 在使用 V 值之前,能立即读到 V 的最新值(排除B正在用使用变量V)。
原理:由于 CPU 与内存之间加入了缓存,在进行数据操作时,先将数据从主内存拷贝到高速缓存中,CPU 直接操作的是高速缓存中的数据。但在多处理器下,将可能导致各自的高速缓存数据不一致(这可能是由于可见性问题的由来),为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,而总线嗅探机制是实现缓存一致性的常见机制。
嗅探机制工作原理:每个处理器通过监听在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从主内存中把数据读到处理器缓存中。
嗅探机制工作原理自我理解:实时监听线程修改数据的操作,当一个线程修改了数据,会把这个数据与其他线程中的这个数据逐个比对,若数据不一致,并且这个线程没有正在操作此数据,就会把此数据设置为无效值,当这个线程即将要操作此数据时,就会从主内存中重新拷贝一次,来保证每次操作之前拿到的都是最新的值。
指令重排:
为了提高性能,编译器和处理器常常会对指令做重排序。
一般重排序可以分为如下三种类型:
1. 编译器优化重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2. 指令级并行重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。(可能会出现问题)
3. 内存系统重排序。由于处理器使用高速缓存,这使得加载和存储操作看上去可能是在乱序执行。
有序性原理:使用 volatile 修饰变量时,Java 编译器在生成字节码时,会在指令序列中插入内存屏障(CPU处理器的指令)来禁止CPU处理器重排序。
(1)实现原理:
1.获取主内存中的数据,并多存储一份副本
2.对数据进行运算
3.当这个数据同步回主内存中时,会比较副本与主内存中的数据是否相等,若相等把主内存中的数据进行更新,若不同会从第1步重新开始执行。
总结:CAS算法, 它包含3个参数CAS(V,E,N)。V表示主内存中的变量,E表示预期值(从主内存中拷贝的值),N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。
(2)优点:
保证数据操作的原子性,保证了线程是安全的。
这个算法相对synchronized是比较“乐观的”,它不会像synchronized一样,当一个线程访问共享数据的时候,别的线程都在阻塞。synchronized不管是否有线程冲突都会进行加锁。由于CAS是非阻塞的,它死锁问题天生免疫,并且线程间的相互影响也非常小,更重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,所以它要比锁的方式拥有更优越的性能。
(3)缺点:
多线程操作时每次修改值时,可能多次出现内存值和副本值不同的情况,需要循环执行多次
可能出现ABA问题
(4)ABA问题*:
1.在主内存中有一个变量num
2.线程A对num进行操作时被挂起了
3.这个时候线程B对num进行了+1操作并且成功了
4.又有一个线程C会num进行了-1操作并且成功了
5.这个时候线程A被唤醒了,运行完要将num同步到主内存中时发现num没有发生变化,同步成功了。
总结:如果注重结果的话,是可以不考虑ABA问题的;但是有的需求,需要注重过程,必须考虑ABA的问题。
解决ABA问题:
为了解决ABA问题,一般都会在操作时设置一个int类型版本号(version),每次对内存中变量操作后都让版本号加1。当需要修改变量时,除了比较副本中值和内存值以外,还需要比较版本号是否相同。JDK中AtomicStampedReference就是这种方式,提供了全局int属性。
(1)volatitle保证了可见性, 有序性, cas保证了原子性
(2)源码
- public class AtomicInteger extends Number implements java.io.Serializable {
- //直接操作内存的类
- private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
- //获取属性偏移量, 在内存中的地址
- private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
- //存储值
- private volatile int value;
- //有参构造方法
- public AtomicInteger(int initialValue) {
- value = initialValue;
- }
- //无参构造方法
- public AtomicInteger() {
- }
- //获取值
- public final int get() {
- return value;
- }
- //设置值
- public final void set(int newValue) {
- value = newValue;
- }
- //比较交换
- public final boolean compareAndSet(int expectedValue, int newValue) {
- //参数1: 当前对象 参数2: 属性对象在内存中的地址 参数3: 预期值 参数四: 新值
- return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
- }
- //先获取, 再+1 相当于a++
- public final int getAndIncrement() {
- return U.getAndAddInt(this, VALUE, 1);
- }
- //先+1, 再获取 相当于++a
- public final int incrementAndGet() {
- return U.getAndAddInt(this, VALUE, 1) + 1;
- }
-
- //调用的Unsafe中的方法
- public final int getAndAddInt(Object o, long offset, int delta) {
- int v;
- do {
- //内存中最新的值
- v = getIntVolatile(o, offset);
- //参数1:AtomicInteger对象 参数2:属性内存中的地址 参数3:内存中的值 参数4:新值(变更的值)
- } while (!weakCompareAndSetInt(o, offset, v, v + delta));
- return v;
- }
- }