我的家園

          我的家園

          [本文是我對(duì)Java Concurrency In Practice C11的歸納和總結(jié). ?轉(zhuǎn)載請(qǐng)注明作者和出處, ?如有謬誤, 歡迎在評(píng)論中指正. ]?

          可擴(kuò)展性和Amdahl's law--阿姆達(dá)爾定律

          Scalability describes the ability to improve throughput or capacity when additional resources are added. When tuning for performance, the goal is usually to do the same work with less effort. But for scalability, the goal is to do more work with more resources.

          Amdahl's law描述隨著CPU核心數(shù)的增加, 并發(fā)程序所能到達(dá)的理論上的最大速度. 大多數(shù)并發(fā)程序包含并發(fā)和串行2個(gè)部分, 假設(shè)某個(gè)并發(fā)程序串行部分所占的比例為F, CPU核心數(shù)為N, 則其最大運(yùn)行速度為: speedup = 1 / (F + (1 - F) / N), CPU利用率utilization = speedup / N. 由Amdahl's law可知, 假設(shè)CPU核心數(shù)趨近于無(wú)窮, 并發(fā)程序理論上的最大速度為1/F. ?

          如果一個(gè)并發(fā)程序串行部分比例F = 10%, 那么當(dāng)CPU數(shù)N = 10時(shí), 其speedup為5.3, CPU利用率為53%. 當(dāng)CPU數(shù)N增加到100時(shí), speedup為9.2, CPU利用率為9.2%.?

          Amdahl's law表明, 應(yīng)用的可擴(kuò)展性由程序中串行部分所占的比例決定. 在java中, 串行的主要來(lái)源為排它鎖, 因此, 為增加可擴(kuò)展性, 可以考慮從以下方面著手:

          1. 減少持有鎖的時(shí)間.

          2. 減少鎖粒度.

          3. 降低申請(qǐng)鎖的頻率.

          4. 以非排它型鎖代替排它鎖.

          ?

          減少持有鎖的時(shí)間

          將不必要持有鎖的操作從同步代碼塊中移出可以有效減少持有鎖的時(shí)間, 尤其是那些需要長(zhǎng)時(shí)間運(yùn)行的或者是阻塞的操作. 例如:

          public class AttributeStore {
          	private final Map<String, String> attributes = new HashMap<String, String>();
          
          	/**
          	 * 如果Map中存在name所對(duì)應(yīng)的location, 且location匹配給定的正則表達(dá)式時(shí), 返回true. 否則返回false.
          	 */
          	public synchronized boolean userLocationMatches(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);
          	}
          }

          userLocationMatches方法訪問(wèn)了共享變量attributes, 所以需要同步. 但是同步整個(gè)userLocationMatches方法是沒(méi)有必要的, 只需要同步get操作即可:

          public boolean userLocationMatches(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);
          }

          當(dāng)然, 我們也可以將線程安全的責(zé)任委托為Map對(duì)象, 這樣在AttributeStore類(lèi)中就不需要做同步了:

          public class AttributeStore {
          	/**
          	 * 將線程安全的責(zé)任委托給ConcurrentHashMap
          	 */
          	private final Map<String, String> attributes = new ConcurrentHashMap<String, String>();
          
          	/**
          	 * 如果Map中存在name所對(duì)應(yīng)的location, 且location匹配給定的正則表達(dá)式時(shí), 返回true. 否則返回false.
          	 */
          	public boolean userLocationMatches(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);
          	}
          }

          使用線程安全委托是最好的, 這樣我們就不必?fù)?dān)心對(duì)AttributeStore類(lèi)的后續(xù)修改忘記了加上同步.

          ?

          減少鎖粒度

          通過(guò)鎖的拆分或者分離, 可以減少鎖競(jìng)爭(zhēng)的發(fā)生. 例如:

          public class ServerStatus {
          	public final Set<String> users = new HashSet<String>();
          	public final Set<String> queries = new HashSet<String>();
          
          	public synchronized void addUser(String u) {
          		users.add(u);
          	}
          
          	public synchronized void addQuery(String q) {
          		queries.add(q);
          	}
          
          	public synchronized void removeUser(String u) {
          		users.remove(u);
          	}
          
          	public synchronized void removeQuery(String q) {
          		queries.remove(q);
          	}
          }

          ServerStatus類(lèi)中所以方法都使用this作為鎖. ServerStatus類(lèi)中存在2個(gè)成員users和queries, 使用同一把鎖(this)確保2個(gè)變量的線程安全意味著增加了鎖競(jìng)爭(zhēng)的發(fā)生. 可以將鎖拆分為粒度更小的2個(gè)鎖, 分別保證一個(gè)變量的線程安全:

          public class ServerStatus {
          	public final Set<String> users = new HashSet<String>();
          	public final Set<String> queries = new HashSet<String>();
          
          	public void addUser(String u) {
          		synchronized (users) {
          			users.add(u);
          		}
          	}
          
          	public void addQuery(String q) {
          		synchronized (queries) {
          			queries.add(q);
          		}
          	}
          	//...
          }?

          一個(gè)鎖保證一個(gè)變量的線程安全可以有效的降低鎖競(jìng)爭(zhēng)的發(fā)生. 為了更好的并發(fā)性能, 有時(shí)甚至使用粒度更小的鎖: 使用多個(gè)鎖保證同一個(gè)變量的線程安全. 熟悉ConcurrentHashMap的開(kāi)發(fā)者可能知道, ConcurrentHashMap中使用16個(gè)鎖保證其線程安全, 這樣的機(jī)制使得ConcurrentHashMap具有極好的并發(fā)性能. 以下的StripedMap是對(duì)ConcurrentHashMap的簡(jiǎn)化:

          public class StripedMap {
          	// 使用16個(gè)鎖保證 StripedMap的線程安全
          	private static final int N_LOCKS = 16;
          	private final Node[] buckets;
          	private final Object[] locks;
          
          	private static class Node {
          		// ... 
          	}
          
          	public StripedMap(int numBuckets) {
          		buckets = new Node[numBuckets];
          		locks = new Object[N_LOCKS];
          		for (int i = 0; i < N_LOCKS; i++)
          			locks[i] = new Object();
          	}
          
          	private final int hash(Object key) {
          		return Math.abs(key.hashCode() % buckets.length);
          	}
          
          	public Object get(Object key) {
          		int hash = hash(key);
          		// 使用不同的鎖同步不同的bucket
          		synchronized (locks[hash % N_LOCKS]) {
          			for (Node m = buckets[hash]; m != null; m = m.next)
          				if (m.key.equals(key))
          					return m.value;
          		}
          		return null;
          	}
          
          	public void clear() {
          		for (int i = 0; i < buckets.length; i++) {
          			synchronized (locks[i % N_LOCKS]) {
          				buckets[i] = null;
          			}
          		}
          	}
          	// ... 
          }

          ?

          以非排它型鎖代替排它鎖

          例如使用讀寫(xiě)鎖, 不可變對(duì)象或者原子量等.

          讀寫(xiě)鎖(ReadWriteLock)支持并發(fā)的讀操作. 對(duì)于讀操作頻率遠(yuǎn)大于寫(xiě)操作頻率的應(yīng)用, 讀寫(xiě)鎖的使用可以帶來(lái)并發(fā)性能的改善.

          不可變對(duì)象天然具有線程安全, 不需要使用額外的同步.

          原子量(如AtomicInteger, AtomicLong等)經(jīng)常用作計(jì)數(shù)器, 序列號(hào)生成器等.

          ?






          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 河东区| 广宗县| 江安县| 磐安县| 历史| 永顺县| 穆棱市| 县级市| 且末县| 龙口市| 田东县| 梁平县| 石景山区| 洪洞县| 莆田市| 利辛县| 镇赉县| 维西| 林周县| 渑池县| 济南市| 景德镇市| 锡林郭勒盟| 嫩江县| 新源县| 吴川市| 鄱阳县| 黄陵县| 绍兴市| 大英县| 赤峰市| 泰安市| 海原县| 交城县| 全椒县| 萍乡市| 瑞金市| 三门峡市| 建水县| 榆中县| 弥勒县|