原子性
一系列操作要么成功,要么失败,不存在其中的一部分成功了,一部分失败,和数据库的原子性差不多
正例
public int getState() {
return state;
}
多线程的情况下,不管那个线程获取到state都是1
反例
public int stateIncrement() {
state++;
}
这里涉及三个操作
- 先获取state的值
- 再对state+1
- 最后把值赋给state
A、B线程操作同一个state,假设state=1,当A线程操作到第2步把state+1,这时候让出cpu给B线程执行,B完成整个操作,此时state等于2,这时A线程继续执行完,state还是等于2,与我们的预期不相符合,我们想要的是state等于3,这个方法是线程不安全
类似这种自增操作是线程不安全的,java里面要怎么保证其的线程安全呢?可以使用关键字synchronized进行隐式同步处理,或者使用JUC里面提供的Lock进行显式同步处理,也可以直接JUC里面提供的AtomicInteger、AtomicBoolean、AtomicReference等等Atomic类来处理自增这种操作。
Atomic类里面底层的实现是使用CAS+volatile关键字
CAS(Compare And Swap)保证其原子性
/**
* 如果当前与期望值相同就把当前值设置为要更新的值
* @param expect 期望的值
* @param update 更新的值
* @return true 成功 false 失败
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
用volatile修饰变量,保证其可见性及顺序性
/**
* 值
* volatile 保证其可见性和顺序性
*/
private volatile int value;
一般的套路都是这样
/**
* 死循环保证一定会执行成功 cas用于更新值
用volatile修饰变量,保证其可见性及顺序性
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
可见性
线程会存一份主内中共享的数据,这样就不用每次用到都去主内存中去获取了,在自己本地就有一份,加快处理速度,但是这样会导致,对同一个数据的修改,线程自己修改完,并更新到主内存中,并没有告知其它已经在使用该数据的线程,你们本地内存中的那份数据已经过期无效了,就会出现下面的情况,明明是两次加1操作,但数据只增加了1
前面说到了volatile可以保证可见性,那么它是如何保证的呢
当变量被volatile修饰时,线程对变量的写操作都会立马把刷新到主内存中去,并且还会让所有线程中缓存的该变量值无效,必须重新去主内存中获取
顺序性
int a = 1;
int b = 1;
int c = a + b;
上面的代码,我们可能认识是顺序初始化对应的值,JVM会允许指令的重排序,比如这里的a和b赋值是不相关的,这里改变两者的顺序不会对程序的结果造成影响,但是c的赋值与a和b相关,它肯定是要放在a和b赋值之后,不然会影响到执行结果,JVM会在保证单线程执行结果不变的情况,允许指令的重排序
前面说到了volatile可以保证顺序性,那么它是如何保证的呢
volatile修饰某个变量时,会禁止JVM对其指令的重排序
总结
这里的一些知识点是为了JUC源码解析做铺垫的,后面我会对JUC里面的类进行源码分析。对于volatile的特性,我们先暂时记住这个结论,volatile可以保证可见性及顺序性,但保证不了原子性,再后面我会出一些JVM系列的文章会专门说到这块