前言
這篇文章的營(yíng)養(yǎng)非常有限,只是一個(gè)夜黑風(fēng)高的晚上,突發(fā)奇想,如果我要統(tǒng)計(jì)一個(gè)網(wǎng)站的PV,程序應(yīng)該怎么寫(xiě)呢?
一種挫逼的寫(xiě)法

這是一種無(wú)鎖的寫(xiě)法,很明顯,這個(gè)東西是線程不安全的。我們使用12個(gè)線程,每個(gè)線程執(zhí)行 108次方add的操作,發(fā)現(xiàn)最終的結(jié)果并沒(méi)有得到期望的1.2*109次方。

上面一個(gè)是總數(shù),下面一個(gè)是所消耗的時(shí)間。
synchronized VS cas
很明顯,我們需要一個(gè)鎖來(lái)干這個(gè)事情。
synchronized
synchronized關(guān)鍵字,是Java中一個(gè)同步鎖,主要有一下幾種用法:
- 修飾一個(gè)代碼塊,被修飾的代碼塊稱為同步語(yǔ)句塊,其作用的范圍是大括號(hào){}括起來(lái)的代碼
- 修飾一個(gè)方法,被修飾的方法稱為同步方法,其作用的范圍是整個(gè)方法
- 修飾一個(gè)靜態(tài)的方法,其作用的范圍是整個(gè)靜態(tài)方法。

cas
compareAndSet,如果之前了解過(guò)C語(yǔ)言或者操作系統(tǒng),相信對(duì)cas不會(huì)太陌生,這是一個(gè)原子方法。Java中我們可以使用sun.misc.Unsafe#compareAndSwapLong這個(gè)方法。

對(duì)比
- 測(cè)試環(huán)境:

- 測(cè)試條件:
每個(gè)線程執(zhí)行1千萬(wàn)次add 1操作 - 測(cè)試結(jié)果:(4次取平均值)
| 線程數(shù) | synchronized耗時(shí)(ms) | cas耗時(shí)(ms) | 方法三(ms) |
|---|---|---|---|
| 1 | 296 | 142 | 92 |
| 4 | 2749 | 2611 | 999 |
| 8 | 5797 | 4845 | 1851 |
| 16 | 11223 | 10192 | 3702 |
| 32 | 14949 | 20009 | 7779 |
| 64 | 31415 | 39974 | 13784 |
發(fā)現(xiàn)
- synchronized好快
我們發(fā)現(xiàn)synchronized關(guān)鍵一開(kāi)始落后于cas,但是在后期卻完成反超。synchronized其實(shí)在JDK1.5進(jìn)行一波更新。速度大大的提升。
后面我們?cè)倮^續(xù)深入將jdk對(duì)synchronized的優(yōu)化。
cas在競(jìng)爭(zhēng)激烈的時(shí)候速度反而下降。不難想象反復(fù)的失敗重試。 -
CPU資源問(wèn)題
我們發(fā)現(xiàn)了一個(gè)事情,synchronized執(zhí)行的過(guò)程中,CPU的資源一直上不去,這個(gè)也不難想到原因,因?yàn)槠渌€程一直競(jìng)爭(zhēng)不到鎖,一直處于阻塞的狀態(tài)。
cas模型的CPU基本打滿。
-
cas的優(yōu)化
jdk為我們提供了一個(gè)類(lèi)java.util.concurrent.atomic.AtomicLong,效果可以顯著提高。方法三我就是用這個(gè)測(cè)出來(lái)的。雖然方法二的實(shí)現(xiàn)跟方法三的一模一樣,我最后都直接copy代碼出來(lái)了,但仍然達(dá)不到該效率,估計(jì)是有jvm級(jí)別的優(yōu)化。
當(dāng)然我們可以模擬jvm對(duì)synchronized的優(yōu)化,簡(jiǎn)單的說(shuō),jvm的moniter會(huì)根據(jù)競(jìng)爭(zhēng)的情況而調(diào)整synchronize的鎖,我們按照這一思路,如果cas交換次數(shù)失敗到一定的次數(shù),就阻塞這個(gè)線程。
增加了這個(gè)條件之后耗時(shí)大概減少了40%,CPU的使用降低70%(32線程/64線程條件下),當(dāng)然這個(gè)1000是我胡亂搞出來(lái)的一個(gè)值,但線程數(shù)提升上去后仍然比synchronized慢。
- synchronized是個(gè)好東西
synchronized是一個(gè)非常穩(wěn)定的東西,雖然效率不一定是最佳的。但確實(shí)非常好用,下面再來(lái)認(rèn)真研究研究。


