目录
- volatile 特性
- Sync锁优化方式
- CAS 概念和使用
1 volatile特性
- 1.1 保证变量的可见性
可见性:即变量的变化是否能够观察到;volatile 通过cpu的缓存一致性协议保证变量可见性
代码分析:
**在示代码中,当running == true,m()方法体中while执行条件为真,在测试方法中改变running的值,但是线程t1调用m()方法中的while条件依然为真,这是因为对于变量running,线程t1将running拷贝到自己的工作区,同时主线程也将running拷贝到自己的工作区,t1工作区中的running无法看到主线程方法区中对running的改动
- 使用volatile修饰变量保证变量的可见性
public class VolatileClass {
private /*volatile*/ boolean running = true;
//测试可见性
public static void test01() {
VolatileClass vc = new VolatileClass();
new Thread(vc::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
vc.running = false;
}
public void m() {
System.out.println("m start ");
while (running) {
//业务代码省略
}
System.out.println("m end");
}
}
- 1.2 禁止指令重排序
compiler在对代码进行编译时,对代码优化可能会改变代码顺序。
示例代码时Double Check Lock单例模式(双重检查单例模式)
- 代码分析
双重检查单例模式instance是否需要volatile修饰?
需要! 当两个线程同时获取instance时,在第一次instance == null检查时可能会出现问题,指令重排序导致的问题!
JVM中new 对象时,指令分为三步:申请内存,变量初始化值(此时instatce已不为空),变量赋值给instance。
当t1 t2争用getInstance()时,如果t1执行到第二步,此时t2进行first check ,instance不为空,则t2得到未初始化完毕的instance。
public class DCL {
private static /*volatile*/ DCL instance;//双重检查单例模式 需要加volatile吗?需要
private DCL() {
}
public static void main(String[] args) {
for(int i = 0;i<10;i++){
new Thread(()->{
System.out.println(DCL.getInstance().hashCode());
}).start();
}
}
public static DCL getInstance() {
if (instance == null) { //first check
synchronized (DCL.class) {
if (instance == null) { //second check
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new DCL();
}
}
}
return instance;
}
}
2 Sync锁优化
- 2.1 细化锁
尽量减小锁的粒度,即锁住的代码量,因为加锁性能会下降
- 2.2 粗化锁
当一个方法中加了很多锁,则可以直接将方法锁住
3 CAS
Compare And Set (CAS)
- 3.1 AtomicXXX 类
原子类 利用CAS保证线程安全,例如AtomicInteger类
- 输出结果为10000
public class AtomicXXX {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
List<Thread> threads = new ArrayList<>();
for(int i = 0;i<10;i++){
threads.add(new Thread(()->{
increment();
}));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(count);
}
static void increment(){
for(int i= 0;i<10000;i++){
count.incrementAndGet();
}
}
}
- 3.2 CAS原理
cas(value,Expected,Newvalue)
value 当前值,Expected 期望值,Newvalue新值
如果value != Expected ,继续调用cas;
如果value == Expected,则value = Newvalue;
*CAS可能存在ABA问题
可以通过给value 加时间戳或者版本号解决
A version1
B version2
A version3
对于基本数据类型ABA没有影响,但是对于对象可能产生问题,如果当前值为对象A,然后指向对象B(对象B操作了对象C),最后指向A,此时对C做的操作可能导致问题。
- ABA对于对象引用可能导致的问题可以类比你和女友分手又复合,在分手期间女友做了什么你不知道,属于隐藏风险!