当前位置: 首页 > 后端技术 > Java

先说volatile

时间:2023-04-01 18:39:00 Java

一、作用1.保证线程可见性主线程和T线程共享堆内存中的数据;main和T也有自己的工作区。当访问共享内存的数据标志时,共享内存的标志将被设置复制一份到你的工作空间。比如主线程修改标志位,先在自己的空间,修改后的值会立即修改到共享内存;但是很难控制T线程何时检查共享内存的值是否已经改变。即主线程的修改没有及时反映到T线程,即线程间不可见。给这个变量加上volatile后,可以保证一个线程修改了这个变量后,另一个线程可以马上知道。底层由CPU的缓存一致性协议(MESI)来保证。2、禁止指令重排序指令重排序:CPU原先执行一条指令时,一步步执行;当前的CPU为了提高效率,会并发执行指令,也就是当第一条指令执行到一半的时候,第二条指令可能已经开始执行了,也就是流水线执行。这时候就要求编译器能够处理影响指令重排序的情况。这时候,volatile就派上用场了。拿DCL单例(DoubleCheckLock)讲解/***@作者Java与算法学习:周一*/publicclassSingleton{privatestaticvolatileSingletonINSTANCE;privateSingleton(){}publicstaticSingletongetInstance(){if(INSTANCE==null){//双重检查同步(Singleton.class){if(INSTANCE==null){try{Thread.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}INSTANCE=newSingleton();}}}返回实例;}publicstaticvoidmain(String[]args){for(inti=0;i<100;i++){newThread(()->{System.out.println(Singleton.getInstance().hashCode());})。开始();}}}INSTANCE=newSingleton();(如果有一个变量inta=100)newobject的过程分为3步1)申请内存(赋默认值);a=02)初始化;a=1003)赋值,put给变量赋值;将a的值赋给INSTANCE,即让INSTANCE指向变量a的地址。如果其中有指令重排,则有未初始化的变量,进行赋值操作,即交换2、3两步的位置。;即对象处于半初始化状态,进行赋值操作。当第一个线程(虽然加了锁),执行到newSingleton()时,new了一半;这时候第二个线程来了,首先判断INSTANCE是否为空,因为INSTANCE已经是半初始化状态,里面已经有值了,不再是空值,即第二个线程已经拿到了这个对象,这个线程可以直接使用这个对象,很可能会用到里面的值。本来期望这个值是100,但是这个值是0,如果这个值是订单数的值,那就有问题了。加入volatile后,不允许对该对象的指令重新排序,即赋值操作必须在初始化完成后进行。3、volatile不能保证原子性/***@authorJava与算法学习:Monday*/publicclassT{publicvolatileintcount=0;publicsynchronizedvoidm(){for(inti=0;i<1000;i++){count++;}}publicstaticvoidmain(String[]args){Tt=newT();列表<线程>threadList=newArrayList<>();对于(inti=0;i<10;i++){threadList.add(newThread(t::m));}threadList.forEach((o)->{o.start();});threadList.forEach((o)->{try{o.join();}catch(InterruptedExceptione){e.printStackTrace();}});System.out.println(t.count);}}m方法,如果不加synchronized,count永远不会到10000,原因是当一个线程把count的值改成1的时候,此时第二个和第三个线程进来了,都读到了count为1,修改后写回count加1(即2)。线程修改count之后,count的值只是从1变成了2,所以最后的结果总是小于10000。归根结底,count的值保证了可见性,但是count++本身并不是原子操作(最下面层分为几个步骤)。volatile可以保证线程的可见性,但是不能代替synchronized保证原子性。

最新推荐
猜你喜欢