0%

java | 原子引用

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference

AtomicReference 例子

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j(topic = "c.Test")
public class Run {

public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
DecimalAccountCas decimalAccountCas = new DecimalAccountCas(new BigDecimal("10000"));
Account.demo(decimalAccountCas);
}

}

class DecimalAccountCas implements Account {

private AtomicReference<BigDecimal> balance;

public DecimalAccountCas(BigDecimal balance) {
this.balance = new AtomicReference<>(balance);
}

@Override
public BigDecimal getBlance() {
return balance.get();
}

@Override
public void withdraw(BigDecimal amount) {
while (true) {
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(amount);
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
}

interface Account {
BigDecimal getBlance();

void withdraw(BigDecimal amount);

static void demo(Account account) {
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(new BigDecimal("10"));
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(account.getBlance());
}
}

ABA 问题

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j(topic = "c.Test")
public class Run {

static AtomicReference<String> ref = new AtomicReference<>("A");

public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
String prev = ref.get();
other();
Thread.sleep(1000);
log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
}

private static void other() throws InterruptedException {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
}).start();

Thread.sleep(500);

new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
}).start();
}
}

输出

1
2
3
4
18:41:45.730 [main] DEBUG c.Test - main start...
18:41:45.777 [Thread-0] DEBUG c.Test - change A->B true
18:41:46.279 [Thread-1] DEBUG c.Test - change B->A true
18:41:47.282 [main] DEBUG c.Test - change A->C true

log.debug("change A->C {}", ref.compareAndSet(prev, "C")); 是无法感知到之前的 A 是不是变化了。

AtomicStampedReference

AtomicStampedReference 可以解决 ABA 问题。其原理是加了一个版本号,记录该值是否修改过,要是修改了,就要修改版本号。

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicStampedReference;

@Slf4j(topic = "c.Test")
public class Run {

static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
String prev = ref.getReference();
int stamp = ref.getStamp();
other();
Thread.sleep(1000);
log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}

private static void other() throws InterruptedException {
new Thread(() -> {
int stamp = ref.getStamp();
log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B", stamp, stamp + 1));
}).start();

Thread.sleep(500);

new Thread(() -> {
int stamp = ref.getStamp();
log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A", stamp, stamp + 1));
}).start();
}
}

输出

1
2
3
4
18:47:11.430 [main] DEBUG c.Test - main start...
18:47:11.476 [Thread-0] DEBUG c.Test - change A->B true
18:47:11.977 [Thread-1] DEBUG c.Test - change B->A true
18:47:12.982 [main] DEBUG c.Test - change A->C false

我们可以根据 stamp 获知引用变量中途被更改多少次。

AtomicMarkableReference

有时候,我们并不关心引用变量被更改多少次,只单纯的关心是否被更改过,使用使用 AtomicMarkableReference

1
AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A", true);
请我喝杯咖啡吧~