概览

原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。

原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分,将整个操作视作一个整体是原子性的核心特征。

在java中提供了很多原子类,笔者在此主要把这些原子类分成四大类。

原子更新基本类型或引用类型

如果是基本类型,则替换其值,如果是引用,则替换其引用地址,这些类主要有:

(1)AtomicBoolean

原子更新布尔类型,内部使用int类型的value存储1和0表示true和false,底层也是对int类型的原子操作。

(2)AtomicInteger

原子更新int类型。

(3)AtomicLong

原子更新long类型。

(4)AtomicReference

原子更新引用类型,通过泛型指定要操作的类。

(5)AtomicMarkableReference

原子更新引用类型,内部使用Pair承载引用对象及是否被更新过的标记,避免了ABA问题。

(6)AtomicStampedReference

原子更新引用类型,内部使用Pair承载引用对象及更新的邮戳,避免了ABA问题。

这几个类的操作基本类似,底层都是调用Unsafe的compareAndSwapXxx()来实现,基本用法如下:

private static void testAtomicReference() {
AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.incrementAndGet();
atomicInteger.getAndIncrement();
atomicInteger.compareAndSet(3, 666);
System.out.println(atomicInteger.get()); AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
atomicStampedReference.compareAndSet(1, 2, 1, 3);
atomicStampedReference.compareAndSet(2, 666, 3, 5);
System.out.println(atomicStampedReference.getReference());
System.out.println(atomicStampedReference.getStamp());
}

原子更新数组中的元素

原子更新数组中的元素,可以更新数组中指定索引位置的元素,这些类主要有:

(1)AtomicIntegerArray

原子更新int数组中的元素。

(2)AtomicLongArray

原子更新long数组中的元素。

(3)AtomicReferenceArray

原子更新Object数组中的元素。

这几个类的操作基本类似,更新元素时都要指定在数组中的索引位置,基本用法如下:

private static void testAtomicReferenceArray() {
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
atomicIntegerArray.getAndIncrement(0);
atomicIntegerArray.getAndAdd(1, 666);
atomicIntegerArray.incrementAndGet(2);
atomicIntegerArray.addAndGet(3, 666);
atomicIntegerArray.compareAndSet(4, 0, 666); System.out.println(atomicIntegerArray.get(0));
System.out.println(atomicIntegerArray.get(1));
System.out.println(atomicIntegerArray.get(2));
System.out.println(atomicIntegerArray.get(3));
System.out.println(atomicIntegerArray.get(4));
System.out.println(atomicIntegerArray.get(5));
}

原子更新对象中的字段

原子更新对象中的字段,可以更新对象中指定字段名称的字段,这些类主要有:

(1)AtomicIntegerFieldUpdater

原子更新对象中的int类型字段。

(2)AtomicLongFieldUpdater

原子更新对象中的long类型字段。

(3)AtomicReferenceFieldUpdater

原子更新对象中的引用类型字段。

这几个类的操作基本类似,都需要传入要更新的字段名称,基本用法如下:

private static void testAtomicReferenceField() {
AtomicReferenceFieldUpdater<User, String> updateName = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class,"name");
AtomicIntegerFieldUpdater<User> updateAge = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); User user = new User("tong ge", 21);
updateName.compareAndSet(user, "tong ge", "read source code");
updateAge.compareAndSet(user, 21, 25);
updateAge.incrementAndGet(user); System.out.println(user);
} private static class User {
volatile String name;
volatile int age; public User(String name, int age) {
this.name = name;
this.age = age;
} @Override
public String toString() {
return "name: " + name + ", age: " + age;
}
}

高性能原子类

高性能原子类,是java8中增加的原子类,它们使用分段的思想,把不同的线程hash到不同的段上去更新,最后再把这些段的值相加得到最终的值,这些类主要有:

(1)Striped64

下面四个类的父类。

(2)LongAccumulator

long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等。

(3)LongAdder

long类型的累加器,LongAccumulator的特例,只能用来计算加法,且从0开始计算。

(4)DoubleAccumulator

double类型的聚合器,需要传入一个double类型的二元操作,可以用来计算各种聚合操作,包括加乘等。

(5)DoubleAdder

double类型的累加器,DoubleAccumulator的特例,只能用来计算加法,且从0开始计算。

这几个类的操作基本类似,其中DoubleAccumulator和DoubleAdder底层其实也是用long来实现的,基本用法如下:

private static void testNewAtomic() {
LongAdder longAdder = new LongAdder();
longAdder.increment();
longAdder.add(666);
System.out.println(longAdder.sum()); LongAccumulator longAccumulator = new LongAccumulator((left, right)->left + right * 2, 666);
longAccumulator.accumulate(1);
longAccumulator.accumulate(3);
longAccumulator.accumulate(-4);
System.out.println(longAccumulator.get());
}

问题

关于原子类的问题,笔者整理了大概有以下这些:

(1)Unsafe是什么?

(3)Unsafe为什么是不安全的?

(4)Unsafe的实例怎么获取?

(5)Unsafe的CAS操作?

(6)Unsafe的阻塞/唤醒操作?

(7)Unsafe实例化一个类?

(8)实例化类的六种方式?

(9)原子操作是什么?

(10)原子操作与数据库ACID中A的关系?

(11)AtomicInteger怎么实现原子操作的?

(12)AtomicInteger主要解决了什么问题?

(13)AtomicInteger有哪些缺点?

(14)ABA是什么?

(15)ABA的危害?

(16)ABA的解决方法?

(17)AtomicStampedReference是怎么解决ABA的?

(18)实际工作中遇到过ABA问题吗?

(19)CPU的缓存架构是怎样的?

(20)CPU的缓存行是什么?

(21)内存屏障又是什么?

(22)伪共享是什么原因导致的?

(23)怎么避免伪共享?

(24)消除伪共享在java中的应用?

(25)LongAdder的实现方式?

(26)LongAdder是怎么消除伪共享的?

(27)LongAdder与AtomicLong的性能对比?

(28)LongAdder中的cells数组是无限扩容的吗?

关于原子类的问题差不多就这么多,都能回答上来吗?点击下面的链接可以直接到相应的章节查看:

死磕 java魔法类之Unsafe解析

死磕 java原子类之AtomicInteger源码分析

死磕 java原子类之AtomicStampedReference源码分析

杂谈 什么是伪共享(false sharing)?

死磕 java原子类之LongAdder源码分析

彩蛋

原子类系列源码分析到此就结束了,虽然分析的类比较少,但是牵涉的内容非常多,特别是操作系统底层的知识,比如CPU指令、CPU缓存架构、内存屏障等。

下一章,我们将进入“同步系列”,同步最常见的就是各种锁了,这里会着重分析java中的各种锁、各种同步器以及分布式锁相关的内容。


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java原子类之终结篇(面试题)的更多相关文章

  1. 死磕 java同步系列之终结篇

    简介 同步系列到此就结束了,本篇文章对同步系列做一个总结. 脑图 下面是关于同步系列的一份脑图,列举了主要的知识点和问题点,看过本系列文章的同学可以根据脑图自行回顾所学的内容,也可以作为面试前的准备. ...

  2. 死磕 java线程系列之终篇

    (手机横屏看源码更方便) 简介 线程系列我们基本就学完了,这一个系列我们基本都是围绕着线程池在讲,其实关于线程还有很多东西可以讲,后面有机会我们再补充进来.当然,如果你有什么好的想法,也可以公从号右下 ...

  3. 死磕 java集合之终结篇

    概览 我们先来看一看java中所有集合的类关系图. 这里面的类太多了,请放大看,如果放大还看不清,请再放大看,如果还是看不清,请放弃. 我们下面主要分成五个部分来逐个击破. List List中的元素 ...

  4. 死磕 java同步系列之AQS起篇

    问题 (1)AQS是什么? (2)AQS的定位? (3)AQS的实现原理? (4)基于AQS实现自己的锁? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为Jav ...

  5. 死磕 java同步系列之AQS终篇(面试)

    问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为 ...

  6. 【死磕Java并发】----- 死磕 Java 并发精品合集

    [死磕 Java 并发]系列是 LZ 在 2017 年写的第一个死磕系列,一直没有做一个合集,这篇博客则是将整个系列做一个概览. 先来一个总览图: [高清图,请关注"Java技术驿站&quo ...

  7. 死磕 java同步系列之volatile解析

    问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile可以说是Java ...

  8. 死磕 java同步系列之redis分布式锁进化史

    问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...

  9. 【死磕Java并发】-----J.U.C之AQS:CLH同步队列

    此篇博客全部源代码均来自JDK 1.8 在上篇博客[死磕Java并发]-–J.U.C之AQS:AQS简单介绍中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列. CLH同步队列是一个F ...

随机推荐

  1. poj1417 True Liars[并查集+背包]

    有一点小转化的题,在设计dp状态时还是有点费脑筋的. 地址. 依题意,首先可以知道肯定要扩展域的并查集(明摆着的嘛).一个"好人"域,一个"坏人"域,每句话分两 ...

  2. 数据结构与算法(2)----->字符串

    1. 字符串的一些特点 1.1  广泛性 (1)字符串可以看作是字符类型的数组----->所以可能会涉及排序+查找; (2)很多问题都可以转化为字符串类型的方法去解决; 需要注意的是:用java ...

  3. POJ2182:Lost Cows

    浅谈线段树和树状数组:https://www.cnblogs.com/AKMer/p/9946944.html 题目传送门:http://poj.org/problem?id=2182 线段树,倒着确 ...

  4. 创建oracle数据库图示(一步一步教你安装oracle)

    123456 密码 版权声明:本文为博主原创文章,未经博主允许不得转载.

  5. synchronized用法详解

    1.介绍 Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中的这个加锁同步代 ...

  6. day1 java基础回顾-多线程

    启动线程方式 方式一:继承Thread. 1. 自定义一个类继承Thread类. 2. 重写Thread的run方法,把自定义线程的任务代码定义在run方法上. 3. 创建Thread子类的对象,并且 ...

  7. CloseHandle()函数的使用

    CloseHandle()函数的使用?? 很多程序在创建线程都这样写的:............ThreadHandle = CreateThread(NULL,0,.....);CloseHande ...

  8. HRBUST - 1214 NOIP2000提高组 方格取数(多线程dp)

    方格取数 设有N*N的方格图(N<=10),我们将其中的某些方格中填入正整数,而其他的方格中则放人数字0.如下图所示(见样例 ,黄色和蓝色分别为两次走的路线,其中绿色的格子为黄色和蓝色共同走过的 ...

  9. java继承使用的细节问题?

    关于java继承的基本概念就不多说了,下面就说说继承使用应该注意的细节问题? 示例 一: package com.bizvane; class Fu{ public Fu() { System.out ...

  10. 从网络架构方面简析循环神经网络RNN

    一.前言 1.1 诞生原因 在普通的前馈神经网络(如多层感知机MLP,卷积神经网络CNN)中,每次的输入都是独立的,即网络的输出依赖且仅依赖于当前输入,与过去一段时间内网络的输出无关.但是在现实生活中 ...