Java 并发编程之性能和可伸缩性

对性能的思考

对于一个给定的操作,通常会缺乏某一种特定的资源限制它的性能,就像常说的短板理论,比如CPU时钟周期、内存、网络带宽、I/O带宽、数据库请求(这个应该是现在高并发的一个瓶颈)、磁盘空间等等。当操作因为某种资源而受到限制时,我们就以资源+“密集型”命名这个操作,如CPU密集型,数据库密集型、。

相对于单线程来说,多线程在多个CPU存在时,能更好的发挥它的优势,不过如果是单核的情况下会花费更多的开销,比如。线程之间的协调,增加的上下文切换、线程的创建和销毁、以及线程的调度。如果过度的使用多线程,那么这些开销甚至影响这个程序的性能比单线程的性能还差。

所以我们在编写多线程的程序时,应该先考虑的程序的运行环境是否是多线程程序所适应的。

性能和可伸缩性

可伸缩性是指当增加计算资源时,程序的吞吐量或者处理能力相应的增加

在进行性能调优时,我们应尽可能地对计算实现并行化。

三层程序模型:表现层、业务逻辑层和持久化层是彼此独立,如果我们把这三层融合到同一个应用程序中那么性能肯定高于应用程序分为多层次分布到多个系统的性能。但是同时它们的可伸缩性就降低了。

当这种单一的系统 到达自身处理能力的极限时,要进一步提升它的处理能力将非常困难 。因此我们通常倾向于更好的可伸缩性。

评估各种性能权衡因素

避免不成熟的优化,首先使程序正确,然后再提高运行速度-如果它还运行的不够快,就是常说的。如果程序没有出现什么 问题,那么 就不要动它。

常用的优化包括:增加内存使用量以降低延迟、增加开销换取安全性。

换句话说,就是这样做值不值得。有下面几个问题:

更快的含义是什么 ?

该方法在什么 条件下运行得更快?低负载还是高负载?大数据集还是小数据集?能否通过测试结果来验证你的答案?

这些条件在运行环境中发生频率?能否通过测试来验证你的答案?

在其他不同条件的环境中能否使用这里的代码?

最后就是为优化牺牲的一些计算资源值不值得?

不要猜测,以测试为基准。

Amdahl定律

在增加计算资源的情况下,程序在理论上能够实现最高加速比,这个值取决于程序中可并行组件与串行组件所占的比重。假定F是必须被串行执行的部分

Speedup<=1/F+((1-F)/N)

所以需要串行执行的部分越少,可实现的最高加速比越高。

不存在完全并行化的程序,就算最简单的并发程序也有从队列里获取Runnable这个串行部分

比如:在线程数量不断增加时,ConcurrentLinkedQueue能比synchronizedLinkedLIst有两个倍的吞吐率。

因为在第一个队列中,只有对指针的更新操作需要串行执行,第二个则是整个的插入或者删除操作都将串行执行。

上下文切换

即线程的调度。当可运行的线程数量大于CPU的数量的时候发生。将新调度进来的线程执行上下文设置为当前上下文。学过安卓开发的知道安卓有一个非常重要的域就是Context(上下文)。

当线程由于等待某个线程发生竞争的锁而被阻塞时,JVM通过会将这个线程挂起,并允许它交换出去。如果线程频繁地发生阻塞,那么 它们将无法使用完整的调度时间片,那么就发生越多的上下文切换,增加调度开销,并因此降低吞吐量。

大多数通用的处理器中,上下文的切换相当于5k到10k的时钟周期,也就是几微秒。

Unix系统中的vmstat命令和Windows系统的perfmon工具都能报告上下文切换次数以及在内核 中执行时间所占比例等 信息。如果内核占用率超过10%,那么通常表示 调度活动发生很频繁。很可能是由于 I/O或者竞争锁的阻塞引起的。

内存同步

在synchronized和volatile提供的可见性保证 中可能会使用一些特殊指令,即内存栅栏。它将抵制一些编译器优化操作,在内存栅栏中,大多数操作都是不能被重排序的。

非竞争同步是被鼓励采用的。它对应用程序整体性能的影响微乎其微。而有竞争的同步会在破坏安全性的同时,经历一个非常痛苦的除错过程。

现在的JVM能通过优化来去掉一些不会发生竞争的锁,从而减少不必要的同步开销。

比如

synchronized (new Object()) {

		}

上面这个同步通常都会被优化掉。

还有一些优化操作如:

	public String getStoogeNames(){
		List<String> stooges=new ArrayList<String>();
		stooges.add("adf");
		stooges.add("adf");
		stooges.add("adf");
		return stooges.toString();
	}

在上面的这段代码中,至少会将stooges的锁获取释放4次(最后的toString()也是一次,一个智能的运行时编译器会分析这些调用,从而合并锁的获取等操作变成一次的锁获取和释放。并且在第一次执行后把getStoogeNames重新编译为仅返回第一次执行的结果。

非竞争同步带来的开销已经非常小了,所以我们应该将优化重点放在那些发生竞争的地方。

时间: 2024-08-27 17:21:11

Java 并发编程之性能和可伸缩性的相关文章

《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

造成开销的操作包括: 1. 线程之间的协调(例如:锁.触发信号以及内存同步等) 2. 增加的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 运行速度涉及以下两个指标: 某个指定的任务单元需要"多快"才能处理完成.计算资源一定的情况下,能完成"多少"工作. 可伸缩性: 当增加计算资源时(例如:CPU.内存.存储容器或I/O带宽),程序的吞吐量或者处理能力能相应地增加. 2 评估各种性能权衡因素 避免不成熟的优化.首先使程序正

《Java并发编程实战》要点笔记及java.util.concurrent 的结构介绍

买了<java并发编程实战>这本书,看了好几遍都不是很懂,这个还是要在实战中找取其中的要点的,后面看到一篇文章笔记做的很不错分享给大家!! 原文地址:http://blog.csdn.net/cdl2008sky/article/details/26377433 Subsections  1.线程安全(Thread safety) 2.锁(lock) 3.共享对象 4.对象组合 5.基础构建模块 6.任务执行 7.取消和关闭 8.线程池的使用 9.性能与可伸缩性 10.并发程序的测试 11.显

&lt;java并发编程的艺术&gt;读书笔记-第三章java内存模型(一)

一概述 本文属于<java并发编程的艺术>读书笔记系列,继续第三章java内存模型. 二重排序 2.1数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性.数据依赖分下列三种类型: 名称 代码示例 说明 写后读 a = 1;b = a; 写一个变量之后,再读这个位置. 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量. 读后写 a = b;b = 1; 读一个变量之后,再写这个变量. 上面三种情况,只要重排序两个操作的执行顺序,

【Java并发编程二】同步容器和并发容器

一.同步容器 在Java中,同步容器包括两个部分,一个是vector和HashTable,查看vector.HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字synchornized. 另一个是Collections类中提供的静态工厂方法创建的同步包装类. 同步容器都是线程安全的.但是对于复合操作(迭代.缺少即加入.导航:根据一定的顺序寻找下一个元素),有时可能需要使用额外的客户端加锁进行保护.在一个同步容器中,复合操作是安全

《Java并发编程实战》读书笔记

Subsections 线程安全(Thread safety) 锁(lock) 共享对象 对象组合 基础构建模块 任务执行 取消和关闭 线程池的使用 性能与可伸缩性 并发程序的测试 显示锁 原子变量和非阻塞同步机制 一.线程安全(Thread safety) 无论何时,只要多于一个线程访问给定的状态变量.而且其中某个线程会写入该变量,此时必须使用同步来协助线程对该变量的访问. 线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的. 线程安全的实例: (1).一个无状

Java并发编程(三)volatile域

相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Android多线程(一)线程池 Android多线程(二)AsyncTask源代码分析 前言 有时仅仅为了读写一个或者两个实例域就使用同步的话,显得开销过大,volatile关键字为实例域的同步訪问提供了免锁的机制.假设声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被还有一个线程并发更新的. 再讲到volatile关键字之前我们须要了解一下内存模型的相关概念以及并发编程中的三个特性:原子性,可见

Java并发编程--BlockingQueue

概述 BlockingQueue支持两个附加操作的Queue:1)当Queue为空时,获取元素线程被阻塞直到Queue变为非空:2)当Queue满时,添加元素线程被阻塞直到Queue不满.BlockingQueue不允许元素为null,如果入队一个null元素,会抛NullPointerException.常用于生产者消费者模式. BlockingQueue对于不能满足条件的操作,提供了四种处理方式: 1)直接抛异常,抛出异常.如果队列已满,添加元素会抛出IllegalStateExceptio

Java并发编程--Lock

类结构图 Lock概述 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作.synchronized方法或代码块的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁.Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock()).一个获取可中断锁

《Java并发编程实战》/童云兰译【PDF】下载

<Java并发编程实战>/童云兰译[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062521 内容简介 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册.书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险.构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子