Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Case Analysis of Java multithreaded volatile keywords and memory Barrier

2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

本篇内容主要讲解"Java多线程volatile关键字及内存屏障实例分析",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"Java多线程volatile关键字及内存屏障实例分析"吧!

volatile是JVM提供的一种最轻量级的同步机制,因为Java内存模型为volatile定义特殊的访问规则,使其可以实现Java内存模型中的两大特性:可见性和有序性。正因为volatile关键字具有这两大特性,所以我们可以使用volatile关键字解决多线程中的某些同步问题。

volatile的可见性

volatile的可见性是指当一个变量被volatile修饰后,这个变量就对所有线程均可见。白话点就是说当一个线程修改了一个volatile修饰的变量后,其他线程可以立刻得知这个变量的修改,拿到最这个变量最新的值。

结合前一篇文章提到的Java内存模型中线程、工作内存、主内存的交互关系,我们对volatile的可见性也可以这么理解,定义为volatile修饰的变量,在线程对其进行写入操作时不会把值缓存在工作内存中,而是直接把修改后的值刷新回写到主内存,而当处理器监控到其他线程中该变量在主内存中的内存地址发生变化时,会让这些线程重新到主内存中拷贝这个变量的最新值到工作内存中,而不是继续使用工作内存中旧的缓存。

下面我列举一个利用volatile可见性解决多线程并发安全的示例:

public class VolatileDemo { //private static boolean isReady = false; private static volatile boolean isReady = false; static class ReadyThread extends Thread { public void run() { while (!isReady) { } System.out.println("ReadyThread finish"); } } public static void main(String[] args) throws InterruptedException { new ReadyThread().start(); Thread.sleep(1000);//sleep 1秒钟确保ReadyThread线程已经开始执行 isReady = true; }}

上面这段代码运行之后最终会在控制台打印出: ReadyThread finish ,而当你将变量isReady的volatile修饰符去掉之后再运行则会发现程序一直运行而不结束,而控制台也没有任何打印输出。

我们分析下这个程序:初始时isReady为false,所以ReadyThread线程启动开始执行后,它的while代码块因标志位isReady为false会进入死循环,当用volatile关键字修饰isReady时,main方法所在的线程将isReady修改为true之后,ReadyThread线程会立刻得知并获取这个最新的isReady值,紧接着while循环就会结束循环,所以最后打印出了相关文字。而当未用volatile修饰时,main方法所在的线程虽然修改了isReady变量,但ReadyThread线程并不知道这个修改,所以使用的还是之前的旧值,因此会一直死循环执行while语句。

volatile的有序性

有序性是指程序代码的执行是按照代码的实现顺序来按序执行的。

volatile的有序性特性则是指禁止JVM指令重排优化。

我们来看一个例子:

public class Singleton { private static Singleton instance = null; //private static volatile Singleton instance = null; private Singleton() { } public static Singleton getInstance() { //第一次判断 if(instance == null) { synchronized (Singleton.class) { if(instance == null) { //初始化,并非原子操作 instance = new Singleton(); } } } return instance; }}

上面的代码是一个很常见的单例模式实现方式,但是上述代码在多线程环境下是有问题的。为什么呢,问题出在instance对象的初始化上,因为 instance = new Singleton(); 这个初始化操作并不是原子的,在JVM上会对应下面的几条指令:

memory =allocate(); //1. 分配对象的内存空间 ctorInstance(memory); //2. 初始化对象 instance =memory; //3. 设置instance指向刚分配的内存地址

上面三个指令中,步骤2依赖步骤1,但是步骤3不依赖步骤2,所以JVM可能针对他们进行指令重拍序优化,重排后的指令如下:

memory =allocate(); //1. 分配对象的内存空间 instance =memory; //3. 设置instance指向刚分配的内存地址ctorInstance(memory); //2. 初始化对象

这样优化之后,内存的初始化被放到了instance分配内存地址的后面,这样的话当线程1执行步骤3这段赋值指令后,刚好有另外一个线程2进入getInstance方法判断instance不为null,这个时候线程2拿到的instance对应的内存其实还未初始化,这个时候拿去使用就会导致出错。

所以我们在用这种方式实现单例模式时,会使用volatile关键字修饰instance变量,这是因为volatile关键字除了可以保证变量可见性之外,还具有防止指令重排序的作用。当用volatile修饰instance之后,JVM执行时就不会对上面提到的初始化指令进行重排序优化,这样也就不会出现多线程安全问题了。

volatile使用场景

volatile的可以在以下场景中使用:

当运算结果不依赖变量当前的值,或者能确保只有单一线程修改变量的值的时候,我们才可以对该变量使用volatile关键字 变量不需要与其他状态变量共同参与不变约束

volatile与原子性

volatile关键字能保证变量的可见性和代码的有序性,但是不能保证变量的原子性,下面我再举一个volatile与原子性的例子:

public class VolatileTest { public static volatile int count = 0; public static void increase() { count++; } public static void main(String[] args) { Thread[] threads = new Thread[20]; for(int i = 0; i

< threads.length; i++) { threads[i] = new Thread(() ->

{ for(int j = 0; j

< 1000; j++) { increase(); } }); threads[i].start(); } //等待所有累加线程结束 while (Thread.activeCount() >

1) { Thread.yield(); } System.out.println(count); }}

The above code creates 20 threads, and each thread performs 1000 self-increment operations on the variable count. If this code is concurrent, the result should be 20000, but in the actual operation process, there will often be results less than 20000, because count++ is not an atomic operation.

The count++ increment operation above is equivalent to count=count+1, so the JVM needs to first read the value of count, then add 1 to it, and then reassign the new value to the count variable, so this increment takes a total of three steps.

In the above figure, I draw a simple flow of thread increment operation on count. When a thread wants to increment count, it first reads the value of count, then performs count+1 operation on the basis of the current count value, and finally writes the new value of count back to count.

If thread 2 reads count while thread 1 reads the old value of count and writes back the new value of count, it is obvious that thread 2 reads the old value of count that has not been updated at this time. At this time, two threads have performed +1 operations on the same value, so these two threads do not have an accumulation effect on count. On the contrary, these operations do not violate the definition of volatile, so in this case, using volatile will still have multithread concurrency safety problems.

volatile and memory barrier

Before introducing the visibility and orderliness of volatile, how does JVM implement these two characteristics for volatile keyword? Java memory model is actually implemented through memory barrier.

Memory barriers are also JVM instructions, and Java memory model rearrangement rules require Java compilers to insert specific memory barrier instructions when generating JVM instructions, which prohibit specific instruction reordering.

In addition, the memory barrier also has certain semantics: all writes before the memory barrier must be written back to main memory, and all reads after the memory barrier can obtain the latest results of all writes before the memory barrier (visibility is achieved). Therefore, reordering instructions after the memory barrier to instructions before the memory barrier is not allowed.

The following table is a list of volatile related prohibited instruction rearrangement behaviors:

Ordinary reading and writing can be rearranged can not be rearranged volatile reading can not be rearranged can not be rearranged volatile writing can not be rearranged can not be rearranged can not be rearranged

From the above table we can draw the following conclusions:

When the second operation volatile is written, it cannot be reordered regardless of the first operation. This rule ensures that operations performed before volatile writes are not rearranged after volatile writes.

When the first operation is volatile read, no rearrangement is possible regardless of the second operation. This operation ensures that operations after volatile reads are not rearranged before volatile reads.

When the first operation is volatile write and the second operation is volatile read, no rearrangement is possible.

There are four types of memory barrier instructions available in the JVM:

LoadLoad Load1; LoadLoad; Load2 guarantees that reads of load1 are executed before load2 and subsequent reads Store1; Store2 guarantees that writes of store1 are flushed to main memory before store2 and subsequent writes are executed LoadStore1; LoadStore2 guarantees that reads of load1 are finished before stroe2 and subsequent writes are executed Store1; StoreLoad; Store2 guarantees that reads of load1 are finished before stroe2 and subsequent writes are executed Load2 ensures that the write operation of store1 has been flushed to main memory before load2 and subsequent read operations can be executed

At this point, I believe we have a deeper understanding of "Java multithreaded volatile keyword and memory barrier instance analysis," may wish to actually operate it! Here is the website, more related content can enter the relevant channels for inquiry, pay attention to us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report