谈谈volatile
volatile的作用:
volatile关键字的作用包括:保障可见性,保障有序性。
何为保障可见性,看下面的代码:
package com.mashibing.thread.lock; public class TestVolatile { public static void main(String[] args) { try { Thread8 t = new Thread8(); t.start(); Thread.sleep(1000); t.setRunning(false); System.out.println("已赋值false"); } catch (InterruptedException e) { e.printStackTrace(); } } } class Thread8 extends Thread { private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } public void run() { System.out.println("进入run()了"); while (isRunning) { } System.out.println("线程被停止了"); } }
我们在主线程里面,启动一个线程,然后去修改的isRunning属性,达到中止线程中while循环的目的,但是实际我们运行代码的结果如下:
很明显,线程没有停止。此时我们在isRunning变量加上vo;alite关键字,结果如下:
这就达到了我们的目的了。所以volatile可以保障一个线程修改了共享变量,能够对其他线程保持可见。
何为保障有序性,看下面的代码:
package singleton; /** * @ClassName SingletonDemo2 * @Description 双重检查(比较完美的写法) * @Author liuyi * @Date 2020/6/7 13:23 * @Version 1.0 */ public class SingletonDemo4 { //必须加volatile关键字,防止指令重排 private static volatile SingletonDemo4 instance; private SingletonDemo4(){ } public synchronized static SingletonDemo4 getInstance(){ //为什么要进行双重检查 //比如两个线程同时进入该方法,都拿到instance为空,其中一个拿到锁并new了一个实例, //此时另外一个线程它并不知道你已经new了实例,所以当它拿到锁之后会继续new一个实例 //所以如果在锁里面继续判断一次是很有必须要的 if(instance==null){ synchronized (SingletonDemo4.class){ if(instance==null){ instance = new SingletonDemo4(); } } } return instance; } public static void main(String[] args) { for (int i = 0; i <100 ; i++) { new Thread(()->{ System.out.println(getInstance()); }).start(); } } }
这里是一个经典的单例模式实现方式之一,我们用volatile关键字单例对象,这里为什么加volatile关键字,就是为了防止出现指令重排序,保障有序性。
我们来分析这里为什么需要防止出现指令重排序,
synchronized (SingletonDemo4.class){ if(instance==null){ instance = new SingletonDemo4(); } }
我们来看被锁住的这句代码,instance = new SingletonDemo4(),其实这句话在计算机底层是分为几个步骤实现的,那么就会一种情况,instance先被
写入到共享变量,然后再被初始化。因为,计算机底层的执行顺序是可以重新排列的,这种现象被称作为指令重排序。那么刚好这个时候,有其他的线程走到if(instance==null)
就会再次进行初始化,这就破环了单例。当然这种情况,只有在超高的并发下才可能出现,我们自己做测试很难出现。