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操做儘可能移出同步塊,這樣能夠縮短任務的平均服務時間,從而提升吞吐量。

完。

不吝指正。

相關文章
相關標籤/搜索