volatile学习心得
特点:
1.保证可见性
2.不保证原子性
3.禁止指令重排
<1>验证可见性:
import java.util.concurrent.TimeUnit;
/*
1.验证volatile的可见性
(针对MyDate类的变量number;为了简略,没有声明私有变量)
1.1:加入int numer = 0;number变量之前根本没有添加volatile关键字修饰
2.1:number变量添加volatile关键字修饰
*/
class MyDate
{
volatile int number =0;
public void addTo60(){
this.number = 60;
}
}
public class VolatileDemo
{
public static void main(String args[]){
MyDate myDate = new MyDate();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t come in");
//暂停一会线程
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
//3s后调用addTo66();使得number = 60;
myDate.addTo60();
System.out.println(Thread.currentThread().getName()+"\t updated number value: "+myDate.number);
},"AAA").start();
while(myDate.number == 0){
//1:变量number如果没有加入volatile关键字,主内存number = 60对main线程的工作内存不可见;使得main线程依旧读取其工作内存中的 number = 0;所以一直等这里等待进入无限循环;
2:加了volatile 关键字,主内存的数据更改对各个线程可见
}
//number 3s后被修改,此时跳出循环输出以下信息
System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number value:"+myDate.number);
}
}
-
<不保证原子性 >
-
原子性:不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者分割。需要整体完整;要么同时成功,要么同时失败
如何解决原子性:
1.加synchronized
2.针对多线程下n++的方法;使用JUC下AtomicInteger 类
class MyDate02{
//volatile 不保证原子性验证
volatile int number =0;
public void addplusplus()
{
number++;
}
//AtomicInteger 类在多线程中n++的原子性
AtomicInteger atomicInteger = new AtomicInteger();
public void addmyAtomic(){
atomicInteger.getAndIncrement();
}
}
//synchronized 保证原子性
public synchronized void addplusplus02()
{
number++;
}
public class VolatileDemo02 {
public static void main(String args[]){
MyDate02 myDate02 = new MyDate02();
for (int i = 1; i <=20; i++) {
new Thread(()->{
for (int j = 1; j <=1000; j++) {
myDate02.addplusplus();
myDate02.addmyAtomic();
myDate02.addplusplus02();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值是多少
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t int type,finally number value: "+myDate02.number);
System.out.println(Thread.currentThread().getName()+"\t AtomicInteger type,finally number value: "+myDate02.atomicInteger);
System.out.println(Thread.currentThread().getName()+"\t synchronized int type,finally number value: "+myDate02.atomicInteger);
}
输出结果
<3>禁止指令重排
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分为3种:
1.单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
2.处理器在进行重排序时必须要考虑指令之间的数据依赖性
3.多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测
volatile 实现禁止指令重排。
DCL(Double Check Lock 双端检锁机制)中使用volatile禁止指令重排