1 引入
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。
class VolatileAtomicThread implements Runnable {
// 定义一个int类型的遍历
private int count = 0 ;
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 1000 ; x++) {
count++ ;
System.out.println("count =========>>>> " + count);
}
}
}
public class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
VolatileAtomicThread volatileAtomicThread = new VolatileAtomicThread() ;
// 开启100个线程对count进行++操作
for(int x = 0 ; x < 100 ; x++) {
new Thread(volatileAtomicThread).start();
}
}
}
执行结果:不保证一定是100000
以上问题主要是发生在count++操作上,count++操作不是一个原子性操作,其包含3个步骤:
- 从主内存中读取数据到工作内存
- 对工作内存中的数据进行++操作
- 将工作内存中的数据写回到主内存
存在的问题:线程A、B同时取出count的值,计算后得到相同的结果,然后同时写回,就出现了计算了2次,但是只对count进行了1次修改的情况。
2 volatile原子性测试
// 用volatile修饰上面的count实例变量
private volatile int count = 0 ;
执行结果:依旧不保证一定是100000
**小结:**在多线程环境下,volatile关键字可以保证共享数据的可见性,但是并不能保证对数据操作的原子性(在多线程环境下volatile修饰的变量也是线程不安全的)。
在多线程环境下,要保证数据的安全性,我们还需要使用锁机制。
volatile的使用场景
- 开关控制:利用可见性特点,控制某一段代码执行或者关闭(上面的变量不可见性问题的例子)。
- 多个线程操作共享变量,但是是有一个线程对其进行写操作,其他的线程都是读
3 问题解决
3.1 使用锁机制
我们可以给count++操作添加锁,那么count++操作就是临界区的代码,临界区只能有一个线程去执行,所以count++就变成了原子操作。
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的变量
private volatile int count = 0 ;
private static final Object obj = new Object();
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
synchronized (obj) {
count++ ;
System.out.println("count =========>>>> " + count);
}
}
}
}
3.2 原子类
java
从JDK1.5
开始提供了java.util.concurrent.atomic
包(简称Atomic包
),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。
AtomicInteger
原子型Integer,可以实现原子更新操作
基本使用
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
int get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的变量
private AtomicInteger atomicInteger = new AtomicInteger() ;
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
int i = atomicInteger.getAndIncrement();
System.out.println("count =========>>>> " + i);
}
}
}
4 原子类的原理
利用CAS机制实现线程安全,CAS的全称是: Compare And Swap(比较再交换)
CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?
-
Synchronized是从悲观的角度出发:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。性能较差!!
-
CAS是从乐观的角度出发:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。CAS这种机制我们也可以将其称之为乐观锁。综合性能较好!