java 多线程变量可见性_Java多线程:易变变量,事前关联和内存一致性

java 多线程变量可见性

什么是volatile变量?

volatile是Java中的关键字。 您不能将其用作变量或方法名称。 期。

我们什么时候应该使用它?

哈哈,对不起,没办法。

当我们在多线程环境中与多个线程共享变量时,通常使用volatile关键字,并且我们希望避免由于这些变量在CPU高速缓存中的缓存而导致任何内存不一致错误 。

考虑下面的生产者/消费者示例,其中我们一次生产/消费一件商品:

public class ProducerConsumer {private String value = "";private boolean hasValue = false;public void produce(String value) {while (hasValue) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("Producing " + value + " as the next consumable");this.value = value;hasValue = true;}public String consume() {while (!hasValue) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}String value = this.value;hasValue = false;System.out.println("Consumed " + value);return value;}
}

在上述类中, Produce方法通过将其参数存储到value中并将hasValue标志更改为true来生成一个新值。 while循环检查值标志( hasValue )是否为true,这表示存在尚未使用的新值,如果为true,则请求当前线程进入睡眠状态。 仅当hasValue标志已更改为false时,此睡眠循环才会停止,这仅在consumer方法使用了新值时才有可能。 如果没有新值可用,那么消耗方法将请求当前线程Hibernate。 当Produce方法产生一个新值时,它将终止其睡眠循环,使用它并清除value标志。

现在想象一下,有两个线程正在使用此类的对象–一个正在尝试产生值(写线程),另一个正在使用它们(读线程)。 以下测试说明了这种方法:

public class ProducerConsumerTest {@Testpublic void testProduceConsume() throws InterruptedException {ProducerConsumer producerConsumer = new ProducerConsumer();List<String> values = Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8","9", "10", "11", "12", "13");Thread writerThread = new Thread(() -> values.stream().forEach(producerConsumer::produce));Thread readerThread = new Thread(() -> {for (int i = 0; i > values.size(); i++) {producerConsumer.consume();}});writerThread.start();readerThread.start();writerThread.join();readerThread.join();}
}

该示例在大多数情况下将产生预期的输出,但也很有可能陷入僵局!

怎么样?

让我们谈谈计算机体系结构。

我们知道计算机由CPU和内存单元(以及许多其他部件)组成。 即使主存储器是我们所有程序指令和变量/数据所在的位置,CPU仍可以在程序执行期间将变量的副本存储在其内部存储器(称为CPU缓存)中,以提高性能。 由于现代计算机现在具有不止一个CPU,因此也有不止一个CPU缓存。

在多线程环境中,可能有多个线程同时执行,每个线程都在不同的CPU中运行(尽管这完全取决于底层操作系统),并且每个线程都可以从main复制变量。内存放入相应的CPU缓存中。 当线程访问这些变量时,它们随后将访问这些缓存的副本,而不是主内存中的实际副本。

现在,假设测试中的两个线程在两个不同的CPU上运行,并且hasValue标志已缓存在其中一个(或两个)上。 现在考虑以下执行顺序:

  1. writerThread产生一个值,并将hasValue更改为true。 但是,此更新仅反映在缓存中,而不反映在主存储器中。
  2. readerThread尝试使用一个值,但是hasValue标志的缓存副本设置为false。 因此,即使writerThread产生了一个值,它也无法使用它,因为线程无法脱离睡眠循环( hasValue为false)。
  3. 由于readerThread没有使用新生成的值, writerThread不能继续进行,因为该标志没有被清除,因此它将停留在其Hibernate循环中。
  4. 而且我们手中有一个僵局!

仅当hasValue标志跨所有缓存同步时,这种情况才会改变,这完全取决于基础操作系统。

volatile如何适合此示例?

如果仅将hasValue标志标记为volatile ,则可以确保不会发生这种类型的死锁:

private volatile boolean hasValue = false;

将变量标记为volatile将迫使每个线程直接从主内存中读取该变量的值。 而且,每次对volatile变量的写操作都会立即刷新到主存储器中。 如果线程决定缓存该变量,则它将在每次读/写时与主内存同步。

进行此更改之后,请考虑导致死锁的先前执行步骤:

  1. 作家线程   产生一个值,并将hasValue更改为true。 这次更新将直接反映到主内存中(即使已缓存)。
  2. 读取器线程正在尝试使用一个值,并检查hasValue的值 这次,每次读取都将强制直接从主内存中获取值,因此它将获取写入线程所做的更改。
  3. 阅读器线程使用生成的值,并清除标志的值。 这个新值将进入主内存(如果已缓存,则缓存的副本也将被更新)。
  4. 编写器线程将接受此更改,因为每个读取现在都在访问主内存。 它将继续产生新的价值。

瞧! 我们都很高兴^ _ ^!

这是否所有的易失性行为都迫使线程直接从内存中读取/写入变量?

实际上,它还具有其他含义。 访问易失性变量在程序语句之间建立先发生后关系。

什么是

两个程序语句之间的先发生后关系是一种保证,可确保一个语句写的任何内存对另一条语句可见。

它与

当我们写入一个易失性变量时,它会以后每次读取该相同变量时创建一个事前发生的关系。 因此,在对该易失性变量进行写操作之前执行的所有内存写操作,对于该易失性变量的读取之后的所有语句,随后都将可见。

Err..Ok ....我明白了,但也许是一个很好的例子。

好的,对模糊的定义表示抱歉。 考虑以下示例:

// Definition: Some variables
private int first = 1;
private int second = 2;
private int third = 3;
private volatile boolean hasValue = false;// First Snippet: A sequence of write operations being executed by Thread 1
first = 5;
second = 6;
third = 7;
hasValue = true;// Second Snippet: A sequence of read operations being executed by Thread 2
System.out.println("Flag is set to : " + hasValue);
System.out.println("First: " + first);  // will print 5
System.out.println("Second: " + second); // will print 6
System.out.println("Third: " + third);  // will print 7

假设上面的两个代码片段由两个不同的线程(线程1和2)执行。当第一个线程更改hasValue时 ,它不仅会将此更改刷新到主内存,还将导致前三个写操作(以及其他任何写操作)先前的写入)也要刷新到主存储器中! 结果,当第二个线程访问这三个变量时,它将看到线程1进行的所有写操作,即使它们之前都已被缓存(这些缓存的副本也将被更新)!

这就是为什么我们在第一个示例中也不必用volatile标记变量的原因。 由于我们在访问hasValue之前已写入该变量,并在读取hasValue之后对其进行了读取,因此该变量会自动与主内存同步。

这还有另一个有趣的结果。 JVM以其程序优化而闻名。 有时,它在不更改程序输出的情况下重新排列程序语句以提高性能。 例如,它可以更改以下语句序列:

first = 5;
second = 6;
third = 7;

到这个:

second = 6;
third = 7;
first = 5;

但是,当语句涉及访问volatile变量时,它将永远不会移动发生在volatile写入之后的语句。 这意味着它将永远不会改变:

first = 5;  // write before volatile write
second = 6;  // write before volatile write
third = 7;   // write before volatile write
hasValue = true;

到这个:

first = 5;
second = 6;
hasValue = true;
third = 7;  // Order changed to appear after volatile write! This will never happen!

即使从程序正确性的角度来看,它们似乎都是等效的。 请注意,只要它们都出现在易失性写入之前,仍然允许JVM在它们之间对前三个写入进行重新排序。

同样,JVM也不会更改在读取易失性变量后出现在访问之前的语句的顺序。 这意味着:

System.out.println("Flag is set to : " + hasValue);  // volatile read
System.out.println("First: " + first);  // Read after volatile read
System.out.println("Second: " + second); // Read after volatile read
System.out.println("Third: " + third);  // Read after volatile read

JVM绝不会将其转换为:

System.out.println("First: " + first);  // Read before volatile read! Will never happen!
System.out.println("Fiag is set to : " + hasValue); // volatile read
System.out.println("Second: " + second); 
System.out.println("Third: " + third);

但是,JVM可以肯定它们中最后三个读取的顺序,只要它们在可变读取之后一直出现。

我认为必须为易失性变量付出性能损失。

您说对了,因为易失性变量会强制访问主内存,并且访问主内存总是比访问CPU缓存慢。 它还会阻止JVM对某些程序进行优化,从而进一步降低性能。

我们是否可以始终使用易变变量来维护线程之间的数据一致性?

不幸的是没有。 当多个线程读写同一变量时,将其标记为volatile不足以保持一致性。 考虑以下UnsafeCounter类:

public class UnsafeCounter {private volatile int counter;public void inc() {counter++;}public void dec() {counter--;}public int get() {return counter;}
}

和以下测试:

public class UnsafeCounterTest {@Testpublic void testUnsafeCounter() throws InterruptedException {UnsafeCounter unsafeCounter = new UnsafeCounter();Thread first = new Thread(() -> {for (int i = 0; i < 5; i++) { unsafeCounter.inc();}});Thread second = new Thread(() -> {for (int i = 0; i < 5; i++) {unsafeCounter.dec();}});first.start();second.start();first.join();second.join();System.out.println("Current counter value: " + unsafeCounter.get());}
}

该代码是不言自明的。 我们在一个线程中递增计数器,而在另一个线程中递减计数器相同次数。 运行此测试后,我们希望计数器保持0,但这不能保证。 在大多数情况下,它将为0,在某些情况下,它将为-1,-2、1、2,即[-5、5]范围内的任何整数值。

为什么会这样? 发生这种情况是因为计数器的递增和递减操作都不是原子的-它们不会一次全部发生。 它们都由多个步骤组成,并且步骤顺序相互重叠。 因此,您可以考虑以下增量操作:

  1. 读取计数器的值。
  2. 添加一个。
  3. 写回计数器的新值。

递减操作如下:

  1. 读取计数器的值。
  2. 从中减去一个。
  3. 写回计数器的新值。

现在,让我们考虑以下执行步骤:

  1. 第一个线程已从内存中读取计数器的值。 最初它设置为零。 然后向其中添加一个。
  2. 第二个线程还从内存中读取了该计数器的值,并看到将其设置为零。 然后从中减去一个。
  3. 现在,第一个线程将counter的新值写回内存,将其更改为1。
  4. 现在,第二个线程将计数器的新值写回内存,即-1。
  5. 第一线程的更新丢失。

我们如何防止这种情况?

通过使用同步:

public class SynchronizedCounter {private int counter;public synchronized void inc() {counter++;}public synchronized void dec() {counter--;}public synchronized int get() {return counter;}
}

或使用AtomicInteger :

public class AtomicCounter {private AtomicInteger atomicInteger = new AtomicInteger();public void inc() {atomicInteger.incrementAndGet();}public void dec() {atomicInteger.decrementAndGet();}public int get() {return atomicInteger.intValue();}
}

我个人的选择是使用AtomicInteger作为同步对象,因为只有一个线程可以访问任何inc / dec / get方法,从而大大降低了性能。

意思是不是……..?

对。 使用synced关键字还可以建立语句之间的事前发生关系。 输入同步的方法/块将在它之前出现的语句与该方法/块内部的语句之间建立先发生后关系。 有关建立事前关系的完整列表,请转到此处 。

就暂时而言,这就是我要说的。

  • 所有示例都已上传到我的github存储库中 。

翻译自: https://www.javacodegeeks.com/2015/11/java-multi-threading-volatile-variables-happens-before-relationship-and-memory-consistency.html

java 多线程变量可见性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/336898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql运行在内核空间_思考mysql内核之初级系列6—innodb文件管理 | 学步园

在上一篇里面&#xff0c;bingxi和alex思考了information_schema&#xff0c;这个一直在innodb外围打转。没有进入到innodb的内部。在后续的文章中&#xff0c;以innodb的为主&#xff0c;逐个思考。Bingxi和alex今天了解了fil文件管理。对应的文件为&#xff1a;D:/mysql-5.1.…

pcl_openmap_OpenMap教程第2部分–使用MapHandler构建基本地图应用程序–第1部分

pcl_openmap1.简介 在第一个教程中&#xff0c;我们创建了一个基本的OpenMap GIS应用程序&#xff0c;该应用程序在JFrame中显示一个从文件系统加载的具有一个形状图层的地图。 该教程基于com.bbn.openmap.app.example.SimpleMap 。 在该教程中&#xff0c;我们使用了以下OpenM…

mysql7.5安装教程_CentOS7.5下yum安装MySQL8图文教程

卸载MariaDB1.列出所有安装的MariaDB rpm 包rpm -qa | grep mariadb2.强制卸载rpm -e --nodeps mariadb-libs-5.5.60-1.el7_5.x86_64安装MySQL1. 环境CentOS7.52. 获取MySQL最新版 rpm包yum仓库下载MySQLyum localinstall https://repo.mysql.com//mysql80-community-release-e…

字符串url获取参数_如何从URL查询字符串获取示例参数或将其附加到URL查询字符串(示例)?...

字符串url获取参数让我们剖析几个简单的用例&#xff0c;并查看视图参数的工作原理&#xff08;视图参数名称不是强制性的&#xff0c;以匹配通过URL查询字符串传递的请求参数&#xff0c;但在本文中&#xff0c;我们将重点讨论这种情况&#xff09;&#xff1a; 情况1 在inde…

mysql和sqlserver分页的区别_关于SQLServer和MySQL 查询分页语句区别

首先来定义几个要用到的参数(例子)t_user数据表int currentPage ; //当前页int pageRecord ; //每页显示记录数关于SqlServer数据库分页SQL语句为:String sql "select top "pageRecord " * from t_user where id not in (select top "(currentPage-1)*pag…

java 微型数据库_Java 9代码工具:使用Java微型基准测试工具的实践会话

java 微型数据库用肉眼看&#xff0c;基准测试似乎只是确定执行某些代码需要花费多长时间的简单问题。 但是&#xff0c;通常情况下&#xff0c;这是幼稚的方法。 提供具有准确和可重复结果的有意义的基准并非易事。 在本文中&#xff0c;我们将向您介绍OpenJDK代码工具项目&a…

mysql快速随机_MySQL随机取数据最高效的方法

mysql随机取数据最高效率的方法发现在SQL语句里有一个 ORDER BY rand() 这样的一个语句&#xff0c;这个说是用着方便&#xff0c;但是效率实在是太低了&#xff0c;于是我用了以下的方法来优化&#xff0c;就是用JOIN表的方法来达到这个取随机数据行的方法&#xff0c;你可以用…

部署被测软件应用和中间件_使用FlexDeploy对融合中间件应用程序进行自动化软件测试...

部署被测软件应用和中间件自动化软件测试是任何软件组织都必须执行的强制性活动之一&#xff0c;以保证其产品质量。 但是&#xff0c;此过程通常变得相当复杂&#xff0c;尤其是涉及由多个不同部分组成的现代复杂系统的自动化测试时。 所有这些部分都基于不同的技术&#xff0…

python batch_size_python 實現動態 batch size,多張圖片如何堆疊轉成指針

前陣子有發問&#xff0c;關於 python 動態 batch size 如何實現&#xff0c;目前解決之前問題現在遇到的問題是當我把兩張圖片直接用 numpy concat 堆疊在一起 進行 acl.util.numpy_to_ptr 轉換成指針進行推理後&#xff0c;得到的結果只有第一張圖片是對的&#xff0c;第二張…

投行数据_投行对Java的二十大核心访谈问答

投行数据这是在金融领域&#xff08;主要是在大型投资银行&#xff09;共享Java核心访谈问题和答案的新系列。 在JP Morgan&#xff0c;Morgan Stanley&#xff0c;Barclays或Goldman Sachs上会问许多这些Java面试问题。 银行主要从多线程 &#xff0c; 集合 &#xff0c;序列化…

php中mysql_fetch_row_php中的mysql_fetch_row,mysql_fetch_array,mysql_fetch_object

1.mysql_fetch_rowmysql_fetch_row&#xff0c;这个函数是从结果集中取一行作为枚举数据&#xff0c;从和指定的结果标识关联的结果集中取得一行数据并作为数组返回。每个结果的列储存在一个数组的单元中&#xff0c;偏移量从 0 开始。 注意&#xff0c;这里是从0开始偏移&…

primefaces_通过OmniFaces缓存组件以编程方式缓存PrimeFaces图表

primefaces在这篇文章中&#xff0c;您将看到如何结合PrimeFaces和OmniFaces获得可缓存的图表。 为了使事情变得简单&#xff0c;我们将使用PrimeFaces 折线图。 对于这种图表&#xff0c;我们可以在页面中使用<p&#xff1a;chart />标签和一个简单的托管bean。 因此&am…

mysql开发问题解决_开发过程中mysql常见问题的解决方法

本篇文章给大家带来的内容是关于开发过程中mysql常见问题的解决方法&#xff0c;有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你有所帮助。记录开发过程中遇到mysql相关的问题以及解决方法&#xff0c;长期更新。远程连接问题mysql默认连接只能给本…

javaone_JavaOne 2015继续进行,Java将永远存在(……也许是长篇大论)

javaone好的。 也许我的标题太夸张了。 我确实相信Java&#xff0c;生态系统&#xff0c;社区&#xff0c;虚拟机和母编程语言将会存在很长一段时间。 有什么可以偏离的&#xff1f; 假定在我的酒店房间中花费很多时间&#xff0c;在我的演讲中增加技巧之间&#xff0c;我认为…

hmailserver mysql密码_第二步:点晴MIS系统Email模块hMailServer数据库连接配置指引

点击&#xff1a;windows系统-》开始-》所有程序-》hmailserver-》hMailServer Database Setup&#xff0c;在弹出的对话框中输入默认管理密码“clicksun2010”&#xff0c;进入后按照以下步骤操作&#xff1a;输入登陆密码&#xff1a;clicksun2010&#xff0c;务必使用这个密…

spring3.0异步_在Spring 4.2中更简单地处理异步事务绑定事件

spring3.0异步介绍 如您可能已经知道的&#xff08;例如&#xff0c;从我以前的博客文章中 &#xff09;&#xff0c;不再需要创建一个单独的类来实现带有onApplicationEvent方法的ApplicationListener &#xff0c;以便能够对应用程序事件做出响应&#xff08;包括来自Spring …

java 插入mysql 日期_Java日期-插入数据库

我需要找出一种将带有java.util.Date字段的记录插入数据库的方法&#xff0c;但我陷入了困境。有谁知道我该怎么做&#xff1f;现在我有类似的东西。java.util.Date myDate new java.util.Date("01/01/2009");sb.append("INSERT INTO USERS");sb.append(&…

jboss fuse 教程_JBoss Fuse –使用MVEL将您的静态配置转换为动态模板

jboss fuse 教程最近&#xff0c;我重新发现了我已经忘记的JBoss Fuse功能&#xff0c;并且我认为其他人可能会从此提醒中受益 。 这篇文章将重点放在JBoss Fuse和Fabric8上&#xff0c;但所有正在寻找最小侵入性方法来为其静态配置文件添加一定程度的动态支持的开发人员也可能…

mysql客户库_你应该知道的10个MySQL客户启动选项

大部分服务器管理员知道MySQL数据库管理系统(RDBMS)是高度灵活的软件块&#xff0c;带有范围广阔的启动选项&#xff0c;可以用来修改相关行为。然而&#xff0c;大部分人却不清楚&#xff0c;标准MySQL客户端带有同等大量的启动选项&#xff0c;其中一些在日常MySQL交互作用中…

java实现ldap服务器_Java到LDAP教程(包括如何安装LDAP服务器/客户端)

java实现ldap服务器本教程将向您展示如何编写Java代码以与LDAP交互。 但是在执行此操作之前&#xff0c;我们需要在计算机上设置LDAP服务器和客户端。 如果此时您不确定到底是什么LDAP&#xff0c;建议您使用这篇文章&#xff0c;其中提供了一个很好的定义示例。 &#xff08;…