Java性能权威指南-总结18

news2024/11/14 21:31:23

Java性能权威指南-总结18

  • 线程与同步的性能
    • 线程同步
      • 避免同步
      • 伪共享

线程与同步的性能

线程同步

避免同步

如果同步可以完全避免,那加锁的损失就不会影响应用的性能。 有两种一般性的方式可以应对。其一是在每个线程中使用不同的对象,这样访问对象时就不存在竞争了。为保证线程安全,很多Java对象是同步的,但是它们未必需要共享。Random类就是这样。

另一方面,很多Java对象创建的成本很高,或者是会占用大量内存。以NumberFornat类为例:这个类的实例不是线程安全的,因为需要构建一个java.util.Locale实例,以满足国际化需求,这使得构建新对象的成本非常高。一个程序用一个共享的全局NumberFormat实例也行得通,但是对这个共享对象的访问需要同步。

相反,更好的模式是使用ThreadLocal对象:

	public class Thermometer {
		private static ThreadLocal<NumberFormat> nfLocal = new ThreadLocal() {
			public NumberFormat initialvalue() {
				NumberFormat nf = NumberFormat.getInstance();
				nf.setMinumumIntegerDigits(2);
				return nf;
				}
			}
			
			public String toString() {
				NumberFormat nf = nfLocal.get();
				nf.format();
			}
		}

通过使用一个线程局部变量,总的对象数得到了限制(使对GC的影响最小化),而且每个对象都不会受制于线程竞争。

其二是使用基于CAS的替代方案。在某种意义上,这不像是避免同步,更像是解决不同的问题。但是在这个背景之下,它也可以减少同步带来的性能损失,会得到同样的效果。

基于CAS的保护和传统的同步之间,其差别看上去正适合用微基准测试来测量:编写比较基于CAS的操作和传统同步方法的代码应该并不繁琐。比如,JDK支持很方便地使用基于CAS保护的计数器;Atomiclong及类似的类。微基准测试可以将使用了基于CAS包含的代码与传统的同步做对比。例如以下代码:

	AtomicLong al = new AtomicLong(0);
	public long dooperation() {
		return al.getAndIncrement();
	}

对比以下代码:

	private volatile long al = 0;
	public synchronized dooperation() {
		return al++;
	}

结果表明,使用微基准测试是行不通的。如果只有一个线程(也就不存在竞争的可能性),使用上述代码所做的微基准测试可以合理地估算两种方案在无竞争环境下的代价。但是对于存在竞争的环境,没法提供任何信息(而且如果代码不会存在竞争,那么一开始就不需要考虑线程安全了)。

仍然使用前面的两个代码片段,构造一个使用两个线程的微基准测试,会发现在共享资源上存在极大的竞争。这也并非现实中的情况:在实际的应用中,两个线程总是同步访问共享资源的情形不大可能出现。加入更多线程只是引入了更多并不现实的竞争情况。

在通常情况下,在比较基于CAS的设施和传统的同步时,可以使用如下指导原则:

  • 如果访问的是不存在竞争的资源,那么基于CAS的保护要稍快于传统的同步(虽然完全不使用保护会更快)。
  • 如果访问的资源存在轻度或适度的竞争,那么基于CAS的保护要快于传统的同步(而且往往是快得多)。
  • 随着所访问资源的竞争越来越剧烈,在某一时刻,传统的同步就会成为更高效的选择。在实践中,这只会出现在运行着大量线程的非常大型的机器上。
  • 当被保护的值有多个读取,但不会被写入时,基于CAS的保护不会受竞争的影响。

最后,还是要对代码所运行的真实生产环境做大量的测试,这是什么都代替不了的:只有这时,才能明确地说,到底某个特定方法的哪个实现会更好。不过即使是这样的真实情况,得出的判断也仅适用于当时那些条件。

快速小结

  1. 避免对同步对象的竞争,是缓解同步对性能影响的有效方式之一。
  2. 线程局部变量不会受竞争之苦;对于保存实际不需要在多个线程间共享的同步对象,它们非常理想。
  3. 对于确实需要共享的对象,基于CAS的工具也是避免传统的同步的方式之一。

伪共享

在同步可能的影响方面,有一点很少被讨论到,就是伪共享(false sharing)。在多线程程序中,这个问题过去相当隐蔽,但是随着多核机器成为标配,很多同步性能问题更明显地浮出水面了。伪共享就是一个越来越重要的问题。

伪共享之所以会出现,跟CPU处理其高速缓存的方式有关。考虑一个简单类中的数据:

public class DataHolder {
	public volatile long L1;
	public volatile long l2;
	public volatile long l3;
	public volatile long l4;
}

这里的每个long值都保存在毗邻的内存位置中;比如,11可能保存在0xF20这个内存位置。那么L2会保存在0xF28,L3在0xF2C,以此类推。当程序要操作12时,会有一大块内存被加载到当前所用的某个CPU核上,比如说,从0xF00到0xF80的128字节。如果有第2个线程要操作L3,则会加载同样一段内存到另一个核的缓存行(cache line)中。

大多数情况下,像这样加载邻接的值是有意义的:如果程序访问了对象中的某个特定实例变量,则很有可能会访问邻接的实例变量。如果这些实例变量被加载到当前核的高速缓存中,内存访问就非常快,这是很大的性能优势。

这种模式的缺点是,当程序更新本地缓存中的某个值时,当前的核必须通知其他所有核:这个内存被修改了。其他核必须作废其缓存行,并重新从内存中加载。
如果有多个线程大量使用DataHolder类会发生什么:

	public class ContendedTest extends Thread {
		private static class DataHolder {
			private volatile long l1 = 0;
			private volatile long l2 = 0;
			private volatile long l3 = 0;
			private volatile long l4 = 0;
	}
	private static DataHolder dh = new DataHolder();
	private static long nLoops;
	
	public ContendedTest(Runnable r) {
		super(r);
	}
	
	public static void main(String[] args) throws Exception {
		nLoops = Long.parseLong(args[0]);
		ContendedTest[] tests = new ContendedTest[4];
		tests[0]= new ContendedTest(() -> {
			for (long i = θ; i < nLoops; i++) {
				dh.l1 += i;
			}
		});
		tests[1]= new ContendedTest(() -> {
			for (long i = θ;i < nLoops; i++) {
					dh.l2 += i;
			}
		});
		//……tests[2]和tests[3]类似……
		long then = Systen.currentTimeMillis();
		for (ContendedTest ct: tests) {
			ct.start();
		}
		for (ContendedTest ct: tests) {
			ct.join();
		}
		long now = System.currentTimeMillis();
		System.out.println("Duration:" + (now - then) + "ms");
	}
}

结果并非如此:当一个特定的线程在其循环中写volatile值时,其他每个线程的缓存行都会被作废,内存值必须重新加载。结果如下表所示,性能随着线程数增多而变差了。
在这里插入图片描述
严格来讲,伪共享未必会涉及同步(或volatile)变量:不论何时,CPU缓存中有任何数据被写入了,其他保存了同样范围数据的缓存都必须作废。然而,切记Java内存模型要求数据只是在同步原语(包括CASvolatile构造)结束时必须写入主内存。所以这种情况是最常见的。在这个例子中,如果long变量不是volatile的,那么编译器会将这些值放到寄存器中,不管有多少个线程,测试将在大约7.1秒内执行完毕。

很明显,这是个极端的例子,但是它提出了一个问题:如何检测并纠正伪共享?遗憾的是,并没有清晰、完整的答案。如果幸运的话,目标处理器的厂商会提供用于诊断伪共享的工具。比如,Intel就有一个叫作VTune的程序,可以通过检查缓存未命中事件来检测伪共享。特定的原生分析器(profiler)可能会提供给定代码行的每指令周期数(Cycles Per Instruction,CPI)的相关信息;如果某个循环内的一条简单指令的CPI非常高,可能预示着代码正在等待将目标内存的信息重新加载到CPU缓存中。

另外,检测伪共享还需要一些直觉和实验。如果正常的分析表明,某个特定循环耗时惊人,则需要检查这个循环,看看是否有可能循环内有多个线程正在访问非共享变量。(Intel VTune手册也写道:“避免伪共享的主要手段就是代码检查”。)

要阻止伪共享,需要对代码做些修改。理想的情况是所涉及的变量不会频繁写入。在前面的例子中,计算可以使用局部变量进行,只有最终结果才写回到DataHolder变量。如果随后的写入次数比较少,就不太可能出现对缓存行的竞争,即使所有4个线程在循环结束时同时更新其结果,也不会影响性能。
第2个可能的方案是填充(padding)相关变量,以免其被加载到相同的缓存行中。如果目标CPU有个128字节的缓存,那么像下面这样填充可能会有效果(也可能没有):

public class DataHolder {
	public volatile long l1;
	pubilc long[] dunmy1 = new long[128 / 8];
	public volatile long l2;
	pubilc long[] dummy2 = new long[128 /8];
	public volatile long l3;
	pubilc long[] dummy3 = new long[128 / 8];
	public volatile long l4;
}

像这样使用数组或许行不通,因为JVM可能会重新安排那些实例变量的布局,以便所有的数组紧挨在一起,于是所有的long变量就仍然会紧挨着了。使用基本类型的值来填充该结构,行之有效的可能性更大,但是考虑到所需的变量数目,并不现实。

使用填充来防止伪共享还有其他问题。填充的大小很难预测,因为不同CPU的缓存大小也不同。而且填充很明显会增大问题中的实例,这对垃圾收集器影响很大(当然也取决于所需的实例数)。不过,如果没有算法上的改进方案,填充数据有时会具有明显的优势。

快速小结

  1. 对于会频繁地修改volatile变量或退出同步块的代码,伪共享对性能影响很大。
  2. 伪共享很难检测。如果某个循环看上去非常耗时,可以检查该代码,看看是否与伪共享出现时的模式相匹配。
  3. 最好通过将数据移到局部变量中、稍后再保存来避免伪共享。作为一种替代方案,有时可以使用填充将冲突的变量移到不同的缓存行中。

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

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

相关文章

Selenium教程__界面的刷新、后退、前进操作(4)

本文将介绍如何使用Selenium来实现界面的刷新、后退和前进操作&#xff0c;以便于测试、开发人员能够更好地控制和管理用户界面的交互行为。 通过学习本文内容&#xff0c;您将能够掌握Selenium中相关API的使用方法&#xff0c;并能够灵活地应用到您的项目中&#xff0c;从而提…

机器学习-搭建轻量级机器人模型

在自己的机器上部署一个机器人简直太酷啦&#xff0c;因为模型数据缘故&#xff0c;可能有时候回复会有一点点怪&#xff0c;不过不影响我们探索机器模型的学习&#xff0c;搭建安装完毕&#xff0c;大家就可自行学习源码啦。 这是启动后台的图片。 需要安装环境&#xff1a;p…

vue监听鼠标与键盘事件

mounted () {window.addEventListener(mousedown, this.handleMousedown)//监听鼠标按下window.addEventListener(mouseup, this.handleMouseup)//监听鼠标抬起window.addEventListener(keydown, this.handlekeydown)//监听键盘按下},methods: {// 鼠标按下事件handleMousedown…

GDAL 标记图像连接区域

文章目录 一、简介1.1原始算法1.2修改标记算法二、实现代码三、实现效果参考资料一、简介 1.1原始算法 标记图像连接区域的算法有很多,这里主要实现了一种基于扫描线的改进算法(Suzuki算法),原始算法使用了一种决策树策略,即对二进制图像b(x,y)进行重复传递,在前向和后向…

chatgpt赋能python:Python要点:从入门到精通

Python要点&#xff1a;从入门到精通 Python是一门高级编程语言&#xff0c;是一种解释型、面向对象、动态数据类型的语言。它的设计思想是“代码易读易写”&#xff0c;在数据科学、人工智能、自动化测试、Web开发等领域广泛应用。本文将从入门到精通的角度来介绍Python的要点…

模拟电路系列分享-电阻的选则

目录 概要 整体架构流程 技术名词解释 技术细节 1.阻值的选择 2.不能太小的原因 3.不能太大的原因 4.E系列选择 小结 概要 提示&#xff1a;这里可以添加技术概要 例如&#xff1a; 电阻(Resistor]是我们的老朋友了&#xff0c;从初中就认识。但到目前为止&#xff0c;我们却…

chatgpt赋能python:Python获取电脑IP:教你简单又快速的实现方式

Python获取电脑IP&#xff1a;教你简单又快速的实现方式 如果你是一名网络工程师或者Web开发者&#xff0c;那么获得电脑IP地址对你来说是一个常见的任务。而Python作为一种流行的脚本语言&#xff0c;也提供了多种方法来获取电脑的IP地址。下面我们将介绍三种Python获取电脑I…

深度生成模型系统的实现原理有哪些?未来的挑战是什么?

由于计算机性能的快速提升, 学习可观测样本的概率密度并随机生成新样本的生成模型成为热点。相比于需要学习条件概率分布的判别模型生成模型的训练难度大、模型结构复杂, 但除了能够生成新样本外, 生成模型在图像重构、缺失数据填充、密度估计、风格迁移和半监督学习等应用领域…

【因果图法和决策表】某软件的一个模块的需求规格说明书中描述:(1)年薪制员工:严重过失,扣年终风险金的4%;过失,扣年终风险金的2%。(2)非年薪制员工:严重过失,扣当月薪资的8%;过失,扣当月薪

题目&#xff1a; 某软件的一个模块的需求规格说明书中描述&#xff1a; &#xff08;1&#xff09;年薪制员工&#xff1a;严重过失&#xff0c;扣年终风险金的4%&#xff1b;过失&#xff0c;扣年终风险金的2%。 &#xff08;2&#xff09;非年薪制员工&#xff1a;严重过…

14.RocketMQ之高可用性机制

1.2 高可用性机制 RocketMQ分布式集群是通过Master和Slave的配合达到高可用性的。 Master和Slave的区别&#xff1a;在Broker的配置文件中&#xff0c;参数 brokerId的值为0表明这个Broker是Master&#xff0c;大于0表明这个Broker是 Slave&#xff0c;同时brokerRole参数也会说…

保证水库大坝安全需要做好哪方面的监测

水库安全监测是保证水库大坝安全的重要手段是确保水库大坝安全运行的基础也是做好水库安全管理的重要技术保障。根据《水库大坝安全管理条例》《水库大坝安全监测技术规范》等有关规定&#xff0c;对坝体变形、渗透、应力应变等进行监测&#xff0c;以掌握坝体、坝基和库岸的运…

css增加高斯模糊的效果

实现效果 关键代码 backdrop-filter 当你创造一个元素加上这个属性后&#xff0c;会使得这个元素后面的区域添加效果&#xff08;如模糊或颜色偏移&#xff09; background: rgba(0,0,0,.5);backdrop-filter: blur(10px);

MFC开发第二天 Windows计算器的开发,常用消息解析与处理

文章目录 相关概念陈述一、初始API汇总消息汇总 二、Windows计算器的开发三、常见的消息解析与处理附录 相关概念陈述 INT PTR是一个指向整数类型数据的指针。PTR是指针的缩写&#xff0c;INT是表示整数类型。因此&#xff0c;INT PTR表示指向整数类型的指针。 1、对话框弹出…

Ambari自动部署hadoop

1、Ambari介绍 Apache Ambari项目旨在通过开发用于配置&#xff0c;管理和监控Apache Hadoop集群的软件来简化Hadoop管理. Ambari提供了一个由RESTful API支持的直观&#xff0c;易用的Hadoop管理Web Ul。 Ambari使系统管理员能够: 提供Hadoop集群 Ambari提供了跨任意数量的主…

戴尔游匣笔记本Dell G16 7630原厂Win11系统重装,安装原装出厂OEM预装系统镜像,恢复出厂状态

DELL戴尔游匣笔记本电脑&#xff0c;Dell G16 7630原厂Windows11系统原装OEM预装系统镜像&#xff0c;恢复出厂状态 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、戴尔外星人控制中心等预装程序 链接&#xff1a;https://pan.baidu.com/s/10s7dFd1yHhUsgkKVS-No2A?…

数据库管理-第八十五期 19c OCM之路-准备与环境篇(20230626)

数据库管理 2023-06-26 第八十五期 19c OCM之路-准备与环境篇1 计划2 考试环境3 技巧和注意事项总结 第八十五期 19c OCM之路-准备与环境篇 从去年就有消息传出&#xff0c;OCM将从12c升级到19c&#xff0c;今年12c OCM停考&#xff0c;从业内大佬和OU处了解到其实今年3月30日…

JSP自定义标签【下】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于自定义标签的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 导读&#xff1a; &#x1f4a1;辉辉…

linux-2.6.22.6内核信号量、阻塞和费阻塞

信号量 1.所谓信号量也是资源共享条件下保护资源的一种手段&#xff0c;当定义一个信号量时&#xff0c;这个信号量就相当于一个互斥锁&#xff0c;只能属于一个进程&#xff0c;我们把之前同一设备同一时刻只能由一个进程打开的原子变量操作改成信号量。 代码如下&#xff1…

黑河学院ASP.NET程序设计大作业(1)--搭建页面

一、作业简介&#xff1a; 说明&#xff1a;我分到的是上海交通大学-生命科学技术学院 网址&#xff1a;https://life.sjtu.edu.cn/ 二、作业流程: 前台&#xff1a; 1.确定技术点&#xff08;是自己搭建&#xff0c;还是用框架&#xff09; 2.搭建首页、所有的静态页面&#x…

NET Core添加 Sqlite 数据库

文章目录 相关文章回顾Sqlite安装环境说明Nuget安装测试程序 结尾 相关文章回顾 .net framework 命令行项目使用 sqlite,DbContext C# .NET EF框架 webapi 安装使用sqlite visual studio 2022&#xff0c;ADO.NET 实体数据模型添加 sqlite数据库对象 Sqlite安装 环境说明 …