一、原子性的问题案例1
public class Demo3Volatile {
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
for (int i = 0; i < 5; i++) {
Thread t = new Thread(demo);
t.start();
}
Thread.sleep(1000);
System.out.println(demo.count);
}
static class VolatileDemo implements Runnable {
public volatile int count;
//public volatile AtomicInteger count = new AtomicInteger(0);
public void run() {
addCount();
}
public void addCount() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
}
结果:48109
以上出现原子性问题的原因是count++并不是原子性操作。
count = 5 开始,流程分析:
(1)、线程1读取count的值为5
(2)、线程2读取count的值为5
(3)、线程2加1操作
(4)、线程2最新count的值为6
(5)、线程2写入值到主内存的最新值为6
这个时候,线程1的count为5,线程2的count为6。如果切换到线程1执行,那么线程1得到的结果是6,写入到主内存的值还是6,现在的情况是对count进行了两次加1操作,但是主内存实际上只是加1一次
解决方案:
(1)、使用synchronized
(2)、使用ReentrantLock(可重入锁)
(3)、使用AtomicInteger(原子操作)
使用synchronized
public class Demo3Volatile {
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
for (int i = 0; i < 5; i++) {
Thread t = new Thread(demo);
t.start();
}
Thread.sleep(1000);
System.out.println(demo.count);
}
static class VolatileDemo implements Runnable {
public volatile int count;
//public volatile AtomicInteger count = new AtomicInteger(0);
public void run() {
addCount();
}
public synchronized void addCount() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
}
结果:50000
使用ReentrantLock(可重入锁)
public class Demo3Volatile {
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
for (int i = 0; i < 5; i++) {
Thread t = new Thread(demo);
t.start();
}
Thread.sleep(1000);
System.out.println(demo.count);
}
static class VolatileDemo implements Runnable {
private Lock lock = new ReentrantLock();
public volatile int count;
public void run() {
addCount();
}
public void addCount() {
for (int i = 0; i < 10000; i++) {
lock.lock();
count++;
lock.unlock();
}
}
}
}
使用AtomicInteger(原子操作)
public class Demo3Volatile {
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
for (int i = 0; i < 5; i++) {
Thread t = new Thread(demo);
t.start();
}
Thread.sleep(1000);
System.out.println(demo.count);
}
static class VolatileDemo implements Runnable {
public volatile AtomicInteger count = new AtomicInteger(0);
public void run() {
addCount();
}
public void addCount() {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet();
}
}
}
}
结果:50000
synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。
如果是 count ++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count . addAndGet( 1 );
如果是 JDK 8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好 ( 减少乐观锁的重试次数 ) 。
二、原子性的问题案例2
通过对 AtomicInteger、AtomicBoolean 和 AtomicLong 分析我们发现,这三个原子类只能对单个变量进行原子操作,那么我们如果要对多个变量进行原子操作,这三个类就无法实现了。
那如果要进行多个变量进行原子操作呢?操作方式就是,先把 多个变量封装成一个类,然后通过 AtomicReference 进行操作。
众所周知,对象的引用其实是一个4字节的数字,代表着在JVM堆内存中的引用地址,对一个4字节数字的读取操作和写入操作本身就是原子性的,通常情况下,我们对对象引用的操作一般都是获取该引用或者重新赋值(写入操作),我们也没有办法对对象引用的4字节数字进行加减乘除运算,那么为什么JDK要提供AtomicReference类用于支持引用类型的原子性操作呢?
案例场景:
这里通过设计一个个人银行账号资金变化的场景,逐渐引入AtomicReference的使用,该实例有些特殊,需要满足如下几点要求。
- 个人账号被设计为不可变对象,一旦创建就无法进行修改。
- 个人账号类只包含两个字段:账号名、现金数字。
- 为了便于验证,我们约定个人账号的现金只能增多而不能减少。
根据前两个要求,我们简单设计一个代表个人银行账号的Java类DebitCard,该类将被设计为不可变。
实体类
@Data
@AllArgsConstructor
@ToString
public class DebitCard {
private final String name;
private final int account;
}
AtomicReferenceDemo1
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class AtomicReferenceDemo1 {
// 定义为 volatile 修饰的变量
volatile static DebitCard debitCard = new DebitCard("zhangSan", 10);
public static void main(String[] args) {
IntStream.range(0, 10).forEach(i -> {
new Thread(() -> {
while (true){
// 读取全局的 debitCard 对象
final DebitCard debitCard1 = debitCard;
// 基于全局的 debitCard 加10构建一个新的对象
DebitCard newDc = new DebitCard(debitCard1.getName(), debitCard1.getAccount() + 10);
// 把新建的对象赋值给 全局的变量
debitCard = newDc;
System.out.println(newDc);
try {
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(20));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "T-" + i).start();
});
}
}
控制台打印结果:
DebitCard(name=zhangSan, account=30)
DebitCard(name=zhangSan, account=60)
DebitCard(name=zhangSan, account=40)
DebitCard(name=zhangSan, account=80)
DebitCard(name=zhangSan, account=50)
DebitCard(name=zhangSan, account=20)
DebitCard(name=zhangSan, account=90)
DebitCard(name=zhangSan, account=70)
DebitCard(name=zhangSan, account=110)
DebitCard(name=zhangSan, account=100)
DebitCard(name=zhangSan, account=120)
DebitCard(name=zhangSan, account=130)
DebitCard(name=zhangSan, account=140)
DebitCard(name=zhangSan, account=150)
DebitCard(name=zhangSan, account=160)
DebitCard(name=zhangSan, account=170)
DebitCard(name=zhangSan, account=180)
DebitCard(name=zhangSan, account=190)
DebitCard(name=zhangSan, account=200)
DebitCard(name=zhangSan, account=210)
DebitCard(name=zhangSan, account=220)
DebitCard(name=zhangSan, account=230)
DebitCard(name=zhangSan, account=240)
DebitCard(name=zhangSan, account=250)
DebitCard(name=zhangSan, account=260)
DebitCard(name=zhangSan, account=260)
DebitCard(name=zhangSan, account=270)
DebitCard(name=zhangSan, account=280)
DebitCard(name=zhangSan, account=290)
DebitCard(name=zhangSan, account=300)
DebitCard(name=zhangSan, account=310)
DebitCard(name=zhangSan, account=320)
DebitCard(name=zhangSan, account=330)
DebitCard(name=zhangSan, account=340)
DebitCard(name=zhangSan, account=350)
DebitCard(name=zhangSan, account=360)
根据运行结果我们发现,出现了两处存在问题的地方(上图标红的地方),这个是什么造成的呢?这就涉及到我们之前讲过的,虽然被 volatile 关键字修饰的变量每次更改都可以立即被其他线程看到,但是我们针对对象引用的修改其实至少包含了如下两个步骤,获取该引用和改变该引用 每一个步骤都是原子性的操作,但组合起来就无法保证原子性了。
解决办法1:通过加锁解决
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class AtomicReferenceDemo1 {
// 定义为 volatile 修饰的变量
volatile static DebitCard debitCard = new DebitCard("zhangSan", 10);
public static void main(String[] args) {
IntStream.range(0, 10).forEach(i -> {
new Thread(() -> {
while (true){
synchronized (AtomicReferenceDemo1.class) {
// 读取全局的 debitCard 对象
final DebitCard debitCard1 = debitCard;
// 基于全局的 debitCard 加10构建一个新的对象
DebitCard newDc = new DebitCard(debitCard1.getName(), debitCard1.getAccount() + 10);
// 把新建的对象赋值给 全局的变量
debitCard = newDc;
System.out.println(newDc);
try {
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(20));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "T-" + i).start();
});
}
}
结果如下:
DebitCard(name=zhangSan, account=20)
DebitCard(name=zhangSan, account=30)
DebitCard(name=zhangSan, account=40)
DebitCard(name=zhangSan, account=50)
DebitCard(name=zhangSan, account=60)
DebitCard(name=zhangSan, account=70)
DebitCard(name=zhangSan, account=80)
DebitCard(name=zhangSan, account=90)
DebitCard(name=zhangSan, account=100)
DebitCard(name=zhangSan, account=110)
DebitCard(name=zhangSan, account=120)
DebitCard(name=zhangSan, account=130)
DebitCard(name=zhangSan, account=140)
DebitCard(name=zhangSan, account=150)
DebitCard(name=zhangSan, account=160)
DebitCard(name=zhangSan, account=170)
DebitCard(name=zhangSan, account=180)
DebitCard(name=zhangSan, account=190)
DebitCard(name=zhangSan, account=200)
DebitCard(name=zhangSan, account=210)
DebitCard(name=zhangSan, account=220)
DebitCard(name=zhangSan, account=230)
DebitCard(name=zhangSan, account=240)
DebitCard(name=zhangSan, account=250)
结果发现,虽然解决了原子性的问题,但是性能非常低下,因为synchronized会阻塞。
AtomicReference的非阻塞解决方案
上面是一种阻塞式的解决方案,同一时刻只能有一个线程真正在工作,其他线程都将陷入阻塞,因此这并不是一种效率很高的解决方案,这个时候就可以利用 AtomicReference 的非阻塞原子性解决方案提供更加高效的方式了。
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
public class AtomicReferenceDemo1 {
// 定义为 volatile 修饰的变量
// volatile static DebitCard debitCard = new DebitCard("zhangSan", 10);
static AtomicReference<DebitCard> ref = new AtomicReference(new DebitCard("zhangSan", 10));
public static void main(String[] args) {
IntStream.range(0, 10).forEach(i -> {
new Thread(() -> {
while (true){
// 读取全局的 debitCard 对象
// final DebitCard debitCard1 = debitCard;
DebitCard debitCard1 = ref.get();
// 基于全局的 debitCard 加10构建一个新的对象
DebitCard newDc = new DebitCard(debitCard1.getName(), debitCard1.getAccount() + 10);
// 把新建的对象赋值给 全局的变量
// debitCard = newDc;
if(ref.compareAndSet(debitCard1, newDc)){
System.out.println(Thread.currentThread().getName() + " 当前值为: " + newDc.toString() + " " + System.currentTimeMillis());
}
try {
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(20));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "T-" + i).start();
});
}
}
结果如下:
T-0 当前值为: DebitCard(name=zhangSan, account=20) 1682243679165
T-7 当前值为: DebitCard(name=zhangSan, account=60) 1682243679165
T-1 当前值为: DebitCard(name=zhangSan, account=70) 1682243679165
T-6 当前值为: DebitCard(name=zhangSan, account=50) 1682243679165
T-4 当前值为: DebitCard(name=zhangSan, account=80) 1682243679166
T-3 当前值为: DebitCard(name=zhangSan, account=40) 1682243679165
T-2 当前值为: DebitCard(name=zhangSan, account=30) 1682243679165
T-5 当前值为: DebitCard(name=zhangSan, account=90) 1682243679166
T-8 当前值为: DebitCard(name=zhangSan, account=100) 1682243679166
T-9 当前值为: DebitCard(name=zhangSan, account=110) 1682243679166
T-3 当前值为: DebitCard(name=zhangSan, account=120) 1682243681172
T-5 当前值为: DebitCard(name=zhangSan, account=130) 1682243682169
T-8 当前值为: DebitCard(name=zhangSan, account=140) 1682243685175
T-6 当前值为: DebitCard(name=zhangSan, account=150) 1682243686170
T-1 当前值为: DebitCard(name=zhangSan, account=160) 1682243687180
T-4 当前值为: DebitCard(name=zhangSan, account=170) 1682243689170
T-2 当前值为: DebitCard(name=zhangSan, account=180) 1682243690180
T-1 当前值为: DebitCard(name=zhangSan, account=190) 1682243694195
T-0 当前值为: DebitCard(name=zhangSan, account=200) 1682243695174
T-7 当前值为: DebitCard(name=zhangSan, account=210) 1682243695174
T-4 当前值为: DebitCard(name=zhangSan, account=230) 1682243696185
T-0 当前值为: DebitCard(name=zhangSan, account=220) 1682243696185
T-5 当前值为: DebitCard(name=zhangSan, account=240) 1682243697178
T-0 当前值为: DebitCard(name=zhangSan, account=250) 1682243697194
T-3 当前值为: DebitCard(name=zhangSan, account=260) 1682243698174
T-7 当前值为: DebitCard(name=zhangSan, account=270) 1682243698189
T-6 当前值为: DebitCard(name=zhangSan, account=280) 1682243699184
T-1 当前值为: DebitCard(name=zhangSan, account=290) 1682243699199
T-8 当前值为: DebitCard(name=zhangSan, account=300) 1682243701178
- 原子 AtomicReference ReentrantLock AtomicInteger synchronized原子atomicreference reentrantlock atomicinteger reentrantlock synchronized atomicinteger原子 周期 范围 reentrantlock synchronized semaphore java 原子 问题atomicinteger aba reentrantlock synchronized关键字 关键 线程reentrantlock synchronized java 端面reentrantlock synchronize java 有序性 原子synchronized synchronized有序性 原子java