java并发编程(十): 性能与可伸缩性

性能与可伸缩性:

对性能的思考:

  • 提高性能意味着用更少的资源更多的事情
  • 资源:CPU, 内存,I/O带宽,网络带宽,数据库请求,磁盘空间等。

性能与可伸缩性:

  • 应用程序性能的衡量指标:服务时间延迟时间吞吐率效率可伸缩性容量等。
  • 可伸缩性指:增长资源时,程序的吞吐量或者处理能力相应地增长。

评估各类性能权衡因素:

  • 避免不成熟地优化,首先使程序正确,而后再提升运行速度--若是它还运行得不够快。
  • 以测试为基准,不要猜想。

Amdahl定律:

  • 在增长计算资源的状况下,程序在理论上可以实现最高加速比,这个值取决于程序中可并行组件串行组件的比重。
  • 见图:

  • 对任务队列的串行访问
/**
 * 对任务队列的串行访问
 */
public class WorkThread extends Thread {
	private final BlockingQueue<Runnable> queue;
	
	public WorkThread(BlockingQueue<Runnable> queue){
		this.queue = queue;
	}
	
	public void run(){
		while (true){
			try {
				Runnable task = queue.take(); //此处为程序的串行部分
				task.run();
			} catch (InterruptedException e) {
				break;
			}
		}
	}
}
  • 在全部并发程序中都包含一些串行部分,若是你认为你的程序中不存在串行部分,那么能够再仔细检查一遍。

在各类框架中隐藏的串行部分:

  • 不一样同步队列吞吐量差别

Amdahl定律的应用:

  • 下降锁粒度的技术:锁分解(1个锁分解为2个锁),锁分段(1个锁分解为多个锁)。

线程引入的开销:

  • 对于为了提高性能而引入的线程来讲,并行带来的性能提高必须超过并发致使的开销

上下文切换:

  • 切换上下文须要必定的开销,而在线程调度过程当中须要访问操做系统和JVM共享的数据结构
  • 上下文切换带来的开销因平台而异,通常状况就是几微秒。

内存同步:

  • 同步操做的性能开销包括多个方面,在synchronizedvolatile提供的可见性保证中可能会使用一些特殊命令,即内存栅栏
  • 内存栅栏中,大多数操做都是不能被重排序的。
  • JVM能够经过优化来去掉一些不会发生竞争的锁,如:
//object只能由当前线程所访问,因此会去掉锁
synchronized(new Object()){ 
   // do sth.
}

//局部变量v不会逃逸, 所以线程私有,优化会取消加锁
public String getStoogeNames(){
   Vector<String> v = new Vector<>();
   v.add("Hello");
   v.add("World");
   return v.toString();
}

阻塞:

  • 阻塞的线程将包含两次额外的上下文切换:

       1. 阻塞时,cpu时间片未用完前被交换出去。 java

       2. 请求的锁或资源可用时,再次被切换回来。 ios

减小锁的竞争:

  • 在并发程序中,对可伸缩性的最主要威胁就是独占方式的资源锁
  • 有3中方式能够下降锁的竞争程度:

       1. 减小锁的持有时间web

       2. 下降锁的请求频率数据库

       3. 使用带有协调机制的独占锁windows

减小锁的范围(“快进快出”):

  • 较少锁的持有时间,如将锁无关的代码移出同步块。
/**
 * 没必要要的长时间持有锁
 */
public class AttrbuteStore {
	private final Map<String, String> attributes 
								= new HashMap<String, String>();
	
	/**
	 * synchronized锁住当前对象
	 */
	public synchronized boolean userLocationMatcher(String name, String regexp){
		String key = "users." + name + ".location";
		String location = attributes.get(key);
		if (location == null)
			return false;
		else
			return Pattern.matches(regexp, location);
	}
}

可修改上面的方法: 网络

public boolean userLocationMatcher(String name, String regexp){
	String key = "users." + name + ".location";
	String location = null;
	synchronized(this){
		location = attributes.get(key); //仅锁住共享对象
	}
	if (location == null)
		return false;
	else
		return Pattern.matches(regexp, location);
}

更好的方式是将attributes用并发容器来实现,如ConcurrentHashMap等。 数据结构

减少锁的粒度:

  • 可经过锁分解锁分段技术来实现。
  • 锁分解实例:
/**
 * 多个状态由一个锁来保护
 */
public class ServerStatus {
	public final Set<String> users;
	private final Set<String> queries;
	...
	public synchronized void addUser(String u){
		users.add(u);
	}
	
	public synchronized void addQuery(String q){
		queries.add(q);
	}
}

将锁进行分解: 并发

/**
 * 多个状态由多个锁来保护
 */
public class BetterServerStatus {
	public final Set<String> users;
	private final Set<String> queries;
	...
	public void addUser(String u){
		synchronized(users){
			users.add(u);
		}
	}
	
	public void addQuery(String q){
		synchronized(queries){
			queries.add(q);
		}
	}
}

锁分段:

  • 将所分解技术进一步扩展为对一组独立对象上的锁进行分解,这种状况被称为锁分段

避免热点域:

  • 热点域:好比ConcurrentHashMap.size()求元素个数时,是经过枚举每一个segment.size累加的,若是你说想单独用一个size来保存元素个数,这样size(), isEmpty()这些方法就很简单了,但一样来一个问题,size的修改会很频繁,切须进行锁保护,反而又下降性能,这时的size 就是一个热点域。

一些替代独占锁的方法:

  • 第三种下降竞争锁的影响就是放弃使用独占锁。如并发容器读写锁不可变对象原子变量

监测CPU的利用率:

  • *nix下可用vmstatmpstat, windows下可用perfmon查看cpu情况。
  • cpu利用不充分的缘由:

       1. 负载不充足框架

       2. I/O密集。*nix可用iostat, windows用perfmon性能

       3. 外部限制。如数据库服务,web服务等。

       4. 锁竞争。可经过jstack等查看栈信息。

向对象池说"不":

  • 一般,对象分配操做的开销比同步开销更低

Map性能比较:

  • 建议自行测试一番。

减小上下文切换的开销:

  • 任务在阻塞运行状态间转换时,就至关于一次上下文切换
  • 在任务执行中,尽可能减小其服务时间,如一些I/O操做尽可能移出同步块,这样能够缩短任务的平均服务时间,从而提升吞吐量。

完。

不吝指正。

相关文章
相关标签/搜索