线程安全主要体现在三个方面:原子性,可见性,有序性。
原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作。
可见性:一个线程对主内存的修改可以及时的被其他线程观察到。
有序性:一个线程观察其他线程的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。
- 1 原子性:
- 1.1 AtomicXXX:
- 1.2 锁 synchronized和Lock
- 2 可见性:
- 2.1 synchronized
- 2.2 volatile
- 3 有序性:
- 3.1 volatile、synchronized、Lock
1.原子性
1.1AtomicXXX:
AtomicInteger,AtomicLong,LongAdder,AtomicReference,AtomicReferenceFieldUpdater,AtomicBoolean
使用AtomicStampReference解决CAS的ABA问题
CAS底层源码:
CAS原理就是拿当前的值和底层的值做对比,如果期望的值和底层的值相同,则进行相加操作,否则再重新拿值重新比较直到成功为止。
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;
}
1.2锁
synchronized:依赖JVM
修饰代码块:大括号括起来的代码,作用于调用对象。
修饰方法:整个方法,作用于调用的对象。
修饰静态方法:整个静态方法,作用于所有对象。
修饰类:括号括起来的部分,作用于所有对象。
Lock:依赖特殊的cpu指令,代码实现,ReentrantLock。
原则性对比:
synchronized:不可中断锁,适合竞争不激烈,可读性好。
Lock:可中断锁,多样化同步,竞争激烈也可维持常态。
Aomic:竞争激烈也可维持常态,比Lock性能好,但只能同步一个值。
2.可见性:
导致共享变量在线程间不可见的原因:
1.线程交叉执行
2.重排序结合线程交叉排序
3.共享变量更新后的值没有在工作内存与主存间及时更新
2.1可见性synchronized
JMM规定关于synchronized的两条规定:
1.线程解锁前,必须把共享变量的最新值刷新到主存中
2.线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存重新读取最新的值(注意,加锁解锁是同一把锁)
2.2可见性volatile
通过加入内存屏障和禁止重排序优化来实现的
1.对于volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量刷新到主内存中
2.对于volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
volatile特别适合状态标记量。volatile使用如下:
volatile boolean inited = false;
//线程1
context = loadContext();
inited = true;
//线程2
while(!inited){
sleep();
}
doSomethingWithConfig(context);
3.有序性:
java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
volatile、synchronized、Lock(synchronized、Lock保证每个时刻有一个线程执行同步代码,相当是让线程顺序执行同步代码,自燃保证了有序性)
4.多线程安全性总结:
原子性:Atomic包、CAS算法、synchronized、Lock
可见性:synchronized、volatile
有序性:happens-before
网友评论