四、可见性---volatile
对于可见性,JVM提供了synchronized和volatile。这里我们看volatile。
(1)volatile的可见性是通过内存屏障和禁止重排序实现的
volatile会在写操作时,会在写操作后加一条store屏障指令,将本地内存中的共享变量值刷新到主内存:
volatile在进行读操作时,会在读操作前加一条load指令,从内存中读取共享变量:
(2)但是volatile不是原子性的,进行++操作不是安全的
@Slf4j public class VolatileExample { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static volatile int count = 0; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); add(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count); } private static void add() { count++; } }
执行后发现线程不安全,原因是 执行conut++ 时分成了三步,第一步是取出当前内存 count 值,这时 count 值时最新的,接下来执行了两步操作,分别是 +1 和重新写回主存。假设有两个线程同时在执行 count++ ,两个内存都执行了第一步,比如当前 count 值为 5 ,它们都读到了,然后两个线程分别执行了 +1 ,并写回主存,这样就丢掉了一次加一的操作。
(3)volatile适用的场景
既然volatile不适用于计数,那么volatile适用于哪些场景呢:
1.对变量的写操作不依赖于当前值
2.该变量没有包含在具有其他变量不变的式子中
因此,volatile适用于状态标记量:
线程1负责初始化,线程2不断查询inited值,当线程1初始化完成后,线程2就可以检测到inited为true了。另外,关注Java知音公众号,回复“后端面试”,送你一份面试题宝典!
五、有序性
有序性是指,在JMM中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
可以通过volatile、synchronized、lock保证有序性。
另外,JMM具有先天的有序性,即不需要通过任何手段就可以得到保证的有序性。这称为happens-before原则。
如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性。虚拟机可以随意地对它们进行重排序。
happens-before原则:
- 程序次序规则:在一个单独的线程中,按照程序代码书写的顺序执行。
- 锁定规则:一个unlock操作happen—before后面对同一个锁的lock操作。
- volatile变量规则:对一个volatile变量的写操作happen—before后面对该变量的读操作。
- 线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。
- 线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
- 线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。
- 对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。
- 传递性:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。
总结:
如上,技术在于交流流通,/握手
原文作者:高一级花阿梨喜欢吃榴莲