原子类

原子类

什么是原子类有什么作用

不可分割

一个操作是不可中断的,即使是多线程的情况也可以保证

java.util.concurrent.atomic

原子类的作用和锁类似,是为了保证并发情况下线程安全,不过原子类相比于锁,有一定的优势

原子类粒度更细,原子变量可以把竞争范围缩小到变量级别,这是用户可以获得的最细粒度的情况,通常所得粒度都要大于原子变量的粒度

原子类效率更高,通常使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况

6类原子类纵览

image-20200218100019699

Atomic*基本类型的原子类

以AtomicInteger为例

AtomicInteger:整型原子类,默认值为0

AtomicLong:长整型原子类,默认值为0

AtomicBoolean:布尔型原子类,默认值是false

常用方法

public final int get() 获取当前的值

public final int getAndSet(int newValue) 获取当前的值,并设置新的值

public final int getAndIncrement() 获取当前的值,并自增

public final int getAndDecrement()获取当前的值,并自减

public final int getAndAdd(int delta) 获取当前的值,并加上预期的值

boolean compareAndSet(int expect,int update) 如果当前的数值等于预期值,则以原子方式将该值设置为输入值(update)

AtomicInteger的基本用法,对比非原子类的线程安全问题,使用了原子类之后,不需要加锁,也可以保证线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 描述:演示AtomicInteger的基本用法,对比非原子类的线程安全问题,使用了原子类之后,不需要加锁,也可以保证线程安全。
*/
public class AtomicIntegerDemo1 implements Runnable {

private static final AtomicInteger atomicInteger = new AtomicInteger();

public void incrementAtomic() {
atomicInteger.getAndIncrement();
}

private static volatile int basicCount = 0;

public void incrementBasic() {
basicCount++;
}

public static void main(String[] args) throws InterruptedException {
AtomicIntegerDemo1 r = new AtomicIntegerDemo1();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("原子类的结果:" + atomicInteger.get());
System.out.println("普通变量的结果:" + basicCount);
}

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
incrementAtomic();
incrementBasic();
}
}
}

输出结果

1
2
原子类的结果:20000
普通变量的结果:19294

Atomic*Array数组类型原子类

Atomic*Reference引用类型原子类

AtomicReference类的作用,和AtomicInteger并没有本质的区别,AtomicInteger可以让一个整数保证原子性,而AtomicReference可以让一个对象保证原子性,当然,AtomicReference的功能要比AtomicInteger强,因为一个对象包含很多属性,用法和AtomicInteger类似

把普通变量升级为原子类

用AtomicIntegerFieldUpdater对普通的变量进行升级

应用场景:偶尔需要一个原子get-set操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 描述: 演示AtomicIntegerFieldUpdater的用法
*/
public class AtomicIntegerFieldUpdaterDemo implements Runnable{

static Candidate tom;
static Candidate peter;

public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater
.newUpdater(Candidate.class, "score");

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
peter.score++;
scoreUpdater.getAndIncrement(tom);
}
}

public static class Candidate {

volatile int score;
}

public static void main(String[] args) throws InterruptedException {
tom=new Candidate();
peter=new Candidate();
AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("普通变量:"+peter.score);
System.out.println("升级后的结果"+ tom.score);
}
}

输出结果

1
2
普通变量:19873
升级后的结果20000

注意点

可见范围

不能使用static

Adder累加器

Java8中引入的,是一个相对比较新的类

高并发下LongAdder比AtomicLong效率高,不过本质是空间换时间

竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高并发性

这里演示多线程情况下AtomicLong的性能,有16个线程对同一个AtomicLong累加

由于竞争很激烈,AtomicLong每一次加法,都要flush和refresh,导致消耗资源

AtomicLong的弊端

image-20200220094158524

LongAdder带来的改进

image-20200220101304789

LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计算

base变量:竞争不激烈,直接累加到该变量上

Cell[]数组:竞争激烈,各个线程分散累加到自己的槽Cell[i]中

sum源码分析

1
2
3
4
5
6
7
8
9
10
11
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}

对比AtomicLong和LongAdder

在低征用下,AtomicLong和LongAdder这两个类具有相似的特征,但是在竞争激烈的情况下,LongAdder的预期吞吐量要高得多,但是消耗更多的空间

LongAdder适合的场景是统计求和计数的场景,而且LongAdder基本只提供了add方法,而AtomicLong还具有CAS方法

Accumulator累加器

Accumulator和Adder非常相似,Accumulator就是一个更通用版本的Adder

CAS原理

什么是CAS

并发的场合使用

认为V的值应该是A,如果是的话就把它改成B,如果不是A(说明被别人修改过了),那就不修改了,避免多人同时修改导致出错

CAS有三个操作数:内存值V、预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做,最后返回线程的V值

image-20200220113518122

CPU的特殊指令

CAS的等价代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 描述: 模拟CAS操作,等价代码
*/
public class SimulatedCAS {
private volatile int value;

public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
value = newValue;
}
return oldValue;
}
}

案例演示

两个线程竞争,其中一个落败

应用场景

乐观锁

并发容器

原子类

分析在Java中如何利用CAS实现原子操作

以AtomicInteger为例

AtomicInteger加载Unsafe工具,用来直接操作内存数据

用Unsafe来实现底层操作

用volatile修饰value字段,保证可见性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;

// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

getAndAddInt方法分析

1
2
3
4
5
6
7
8
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}

源码分析过程

AtomicInteger是如何通过CAS实现并发下的累加操作的,以AtomicInteger的getAndAdd方法为突破口

getAndAdd方法

1
2
3
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}

可以看出,这里使用了Unsafe这个类

Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问的,不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作

AtomicInteger加载Unsafe工具,用来直接诶操作内存数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;

// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}

/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}

/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
}

在AtomicInteger数据定义的部分,还获取到了unsafe实例,并定义了valueOffset。再看static块,static块的加载发生于类加载的时候,是最先初始化的,这时候调用unsafe的objectFieldOffset从Atomic类文件中获取value的偏移量,valueOffset其实就是记录value的偏移量的

valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的,这样就能通过unsafe来实现CAS。value是用volatile修饰的,保证了多线程之间看到的value值是同一份的

接下来继续看Unsafe的getAndAddInt方法的实现

1
2
3
4
5
6
7
8
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}

来看看var5获取的是什么

通过调用unsafe的getIntVolatile(var1,var2),这是个native方法,其实就是获取var1中,var2偏移量处的值。var1就是AtomicInteger,var2就是valueOffset,这样就从内存里获取到现在valueOffset处的值了

compareAndSwapInt(var1, var2, var5, var5 + var4)其实换成compareAndSwapInt(obj, offset, expect, update)比较清楚,意思就是如果obj内的value和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update ,如果这一步的CAS没有成功,那就采用自旋的方式继续进行CAS操作

Unsafe的getAndAddInt方法分析:自旋+CAS,在这个过程中,通过compareAndSwapInt比较并更新value值,如果更新失败,重新获取,然后再次更新,直到更新成功

Unsafe类中的compareAndSwapInt

image-20200220151400960

Atomic::cmpxchg(x,addr,e)实现了原子性的比较和替换,x是即将更新的值,e是原值,addr是内存地址

Unsafe类中的compareAndSwapInt方法

方法中先想办法拿到变量value在内存中的地址,通过Atomic::cmpxchg(x,addr,e)实现原子性的比较和替换,其中参数x是即将更新的值,参数e是原内存的值,最终完成了CAS的全过程

缺点

ABA问题

自旋时间过长

final关键字和不变性

什么是不变性(Immutable)

如果对象在被创建后,状态就不能被修改,那么它就是不可变的

具有不变性的对象一定是线程安全的,不需要对其采取任何额外的安全措施,也能保证线程安全

final的作用

早期

早期的Java实现版本中,会将final方法转为内嵌调用,用一个final方法去调用另外一个final方法,把另一个final中的内容全部转移过来,相当于在同一个方法内就完成了整个的工作,而不是方法之间调来调去,方法之间的调用是由性能损耗的,这样可以提高一定的效率

现在

类防止被 继承、方法防止被重写、变量防止被修改

天生是线程安全的,而不需要额外的同步开销

但是从性能的角度已经看不出他的优势了

3种用法

final修饰变量

含义:被final修饰的变量,意味着值不能被修改。如果变量是对象,那么对象的引用不能变,但是对象自身的内容依然可以变化

final instance variable(类中的final属性)

final static varible(类中的static final属性)

final local varible(方法中的final变量)

属性被声明为final后,该变量则只能被赋值一次。且一旦被赋值,final的变量就不能被再被改变,无论如何也不会变

赋值时机

final instance variable(类中的final属性)

第一种是在声明变量的等号右边直接赋值

1
private final int a =6;

第二种是构造函数中赋值

1
2
3
4
5
private final int a;

public FinalVariableDemo(int a) {
this.a = a;
}

第三种就是在类的初始化代码块中赋值(不常用)

1
2
3
4
5
private final int a;

{
a = 7;
}

final static varible(类中的static final属性)

两个赋值时机:除了在声明变量的等号右边直接赋值外,static final变量还可以用static初始化代码块赋值,但是不能用普通的初始代码块赋值

1
private static final int a=4;
1
2
3
4
5
private static final int a;

static {
a = 7;
}

final local varible(方法中的final变量)

由于变量是在方法里,所以没有构造函数,也不存在初始化代码块

1
2
3
4
void testFinal() {
final int b = 7;
int c =b;
}

final local varible不规定赋值时机,只要求在使用前必须赋值,这和方法中的非final变量的要求也是一样的

为什么要规定赋值时机

如果初始化不赋值,后续赋值,就是从null变成所赋的值,这就违背了final不变的原则

final修饰方法

构造方法不允许final修饰

不可被重写,就是不能被override

注意:static方法不能被重写

final修饰类

不可被继承

final注意点

final修饰对象的时候 ,只是对象的引用不可变,而对象本身的属性是可以变化的

final使用原则:良好的编程习惯

不变性和final的关系

不变性并不意味着,简单的用final修饰就是不可变的

  • 对于基本数据类型,确实被final修饰后就具有不变性
  • 但是对于引用类型,需要该对象保证自身被创建后,状态永远不会变才具有不变性,如
1
2
3
4
5
public class Person {

final int age=18;
final String name="Alice";
}
1
2
3
4
5
public class TestFinal {
public static void main(String[] args) {
final Person person = new Person();
}
}

Person实例化对象Person person = new Person()虽然由final修饰,但是必须Person中的属性也是final修饰才具有不变性

满足以下条件对象才是不可变的

  1. 对象创建后,其状态就不能修改
  2. 所有属性都是final修饰的
  3. 对象创建过程中没有发生溢出

栈封闭

把变量写在线程内部

在方法里新建的局部变量,实际上是存储在每个线程私有的栈空间里,而每个栈的栈空间是独立的,所以不会有线程安全问题,这就是著名的“栈封闭”技术,是“线程封闭”技术的一种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 描述: 演示栈封闭的两种情况,基本变量和对象 先演示线程争抢带来错误结果,然后把变量放到方法内,情况就变了
*/
public class StackConfinement implements Runnable {

int index = 0;

public void inThread() {
int neverGoOut = 0;
//这个synchronized完全没有意义的,会被编译器优化掉
//synchronized (this) {
for (int i = 0; i < 10000; i++) {
neverGoOut++;
}
//}

System.out.println("栈内保护的数字是线程安全的:" + neverGoOut);
}

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
index++;
}
inThread();
}

public static void main(String[] args) throws InterruptedException {
StackConfinement r1 = new StackConfinement();
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r1);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(r1.index);
}
}

输出结果

1
2
3
栈内保护的数字是线程安全的:10000
栈内保护的数字是线程安全的:10000
16157

面试题

1
2
3
4
5
6
7
8
9
10
11
12
public class FinalStringDemo1 {

public static void main(String[] args) {
String a = "wukong2";
final String b = "wukong";
String d = "wukong";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
}
}

输出结果

1
2
true
false

final String b = "wukong"在变量b加上final修饰之后,编译期间就知道它的准确值,而且b永远都不会变化,所以编译器会把他当做常量来使用,

  1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  2. 只要其中有一个是变量,结果就在堆中。
  3. 如果拼接的结果调用intern()方法,返回值就在常量池中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FinalStringDemo2 {

public static void main(String[] args) {
String a = "wukong2";
final String b = getDashixiong();
String c = b + 2;
System.out.println(a == c);

}

private static String getDashixiong() {
return "wukong";
}
}

输出结果

1
false
文章目录
  1. 1. 原子类
    1. 1.1. 什么是原子类有什么作用
    2. 1.2. 6类原子类纵览
    3. 1.3. Atomic*基本类型的原子类
      1. 1.3.1. 常用方法
    4. 1.4. Atomic*Array数组类型原子类
    5. 1.5. Atomic*Reference引用类型原子类
    6. 1.6. 把普通变量升级为原子类
    7. 1.7. Adder累加器
      1. 1.7.1. 对比AtomicLong和LongAdder
    8. 1.8. Accumulator累加器
  2. 2. CAS原理
    1. 2.1. 什么是CAS
      1. 2.1.1. CPU的特殊指令
      2. 2.1.2. CAS的等价代码
    2. 2.2. 案例演示
    3. 2.3. 应用场景
    4. 2.4. 分析在Java中如何利用CAS实现原子操作
      1. 2.4.1. 源码分析过程
    5. 2.5. 缺点
    6. 2.6. final关键字和不变性
      1. 2.6.1. 什么是不变性(Immutable)
      2. 2.6.2. final的作用
        1. 2.6.2.1. 早期
        2. 2.6.2.2. 现在
      3. 2.6.3. 3种用法
        1. 2.6.3.1. final修饰变量
        2. 2.6.3.2. 为什么要规定赋值时机
        3. 2.6.3.3. final修饰方法
        4. 2.6.3.4. final修饰类
      4. 2.6.4. final注意点
      5. 2.6.5. 不变性和final的关系
        1. 2.6.5.1. 满足以下条件对象才是不可变的
      6. 2.6.6. 栈封闭
      7. 2.6.7. 面试题
|