③. volatile特性
①. 保证可见性
- ①. 保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可以看到
- ②. 代码展示
不加volatile,没有可见性,程序无法停止
加了volatile,保证可见性,程序可以停止
/* 验证volatile的可见性: 1.加入int number=0; number变量之前没有添加volatile关键字修饰,没有可见性 2.添加了volatile,可以解决可见性问题 * */ class Resource{ //volatile int number=0; volatile int number=0; public void addNumber(){ this.number=60; } } public class Volatile_demo1 { public static void main(String[] args) { Resource resource=new Resource(); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t coming "); try {TimeUnit.SECONDS.sleep(4);}catch (InterruptedException e){e.printStackTrace();} resource.addNumber(); System.out.println(Thread.currentThread().getName()+"\t update "+resource.number); },"线程A").start(); //如果主线程访问resource.number==0,那么就一直进行循环 while(resource.number==0){ } //如果执行到了这里,证明main现在通过resource.number的值为60 System.out.println(Thread.currentThread().getName()+"\t"+resource.number); } }
③. 上述代码原理解释
没有添加volatile关键字,线程A对共享变量改变了以后(number=60),主线程(这里的线程B)访问number的值还是0,这就是不可见
添加volatile之后,线程A对共享数据进行了改变以后,那么main线程再次访问,number的值就是改变之后的number=60
②. 不保证原子性
- ①. 代码展示
(我们对20个线程进行循环100次的操作)
public class Volatile_demo3 { public static void main(String[] args) { /* System.out.println(Thread.activeCount());*/ AutoResource autoResource=new AutoResource(); //20个线程每个线程循环100次 for (int i = 1; i <=20; i++) { new Thread(()->{ for (int j = 1; j <=100; j++) { autoResource.numberPlusPlus(); autoResource.addAtomicInteger(); } },String.valueOf(i)).start(); } //需要等待上面20个线程都全部计算完后,再用main线程取得的最终的结果值是多少 //默认有两个线程,一个main线程,二是后台gc线程 while(Thread.activeCount()>2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+"\t int type"+autoResource.number); System.out.println(Thread.currentThread().getName()+"\t AutoInteger type"+autoResource.atomicInteger.get()); } } class AutoResource{ volatile int number=0; public void numberPlusPlus(){ number++; } //使用AutoInteger保证原子性 AtomicInteger atomicInteger=new AtomicInteger(); public void addAtomicInteger(){ atomicInteger.getAndIncrement(); } }
②. 对于一读一写操作,不会有数据问题
(假设主内存的共享变量number=1,需要对主内存的number++处理,对于两个线程t1、t2如果是一读一写的操作(不会有数据丢失的情况),某一时刻,t1抢到CPU的执行权,将共享数据1读回t1的工作内存,进行number++的操作,这个时候number=2,将2从工作内存写回到主内存中。写回后马上通知t2线程,将number=2读到t2的工作线程)
③. 对于两个写,会出现数据问题
(假设主内存的共享变量number=0,需要对主内存进行10次的number++处理,最终的结果就是10,对于两个线程t1、t2如果是两个写的操作(会造成数据丢失的情况),t1和t2将主内存的共享数据读取到各自的工作内存去,某一时刻,t1线程抢到CPU的执行权,进行
number++的处理,将工作内存中的number=1写回到主内存中,就在这一刻,t2也抢到CPU执行权,进行number++的处理,这个时候number++后的结果也等于1,t1将number=1写回到主内存中去,并通知t2线程,将主内存中的number=1读到t2的工作内存中去,这个时候对于t2,它之前也进行了一次number++的操作将会无效,回重新进行一次number++的操作。这也数据也就写丢了一次,那么10次number++后的结果也就不会等于10)
read-load-use 和 assign-store-write 成为了两个不可分割的原子操作,但是在use和assign之间依然有极小的一段真空期,有可能变量会被其他线程读取,导致写丢失一次.