魔法類的玄幻小說中,經常有個“豬腳”特別厲害,別人都用頂級的寒冰箭或者大火球,他不用,他只用一級的寒冰箭,恰到好處的使用,瞬發的使用,特別厲害。
為什么他能做到呢?因為他悟出了一種叫做“神之本源”或者“力量之源”的東西,掌握了魔法的本質,操控程度達到了極致,故而就厲害到了極致,成了“豬腳”。
本篇的講座,對于Java并發來說,也是這樣一種東西,讓我們從最底層,從硬件級別,了解Java并發的本質,就好像我們掌握了“神之本源”。
# 現代計算機
先講一下現代計算機(2015年之前)的發展現狀,架構的解決方案、語言的低級特性,本質上是基于硬件的當前現狀產生的。
### 計算
現代計算機中,已經不追求CPU的時鐘頻率更高,而是朝著多核的方向發展。因為CPU已經快得離譜,追求更快對于日常應用意義不大,故而更追求多核、并行計算。
在Core 2 3.0 GHz上,大部分簡單指令的執行只需要一個時鐘周期,也就是1/3納秒。
| **數據讀取位置** | **花費CPU時鐘周期** | **花費的時間(單位納秒)** |
|-----|-----|-----|
| 寄存器 | 1 cycle | 1/3 |
| L1 Cache | 3-4 cycles | 0.5-1 |
| L2 Cache | 10-20 cycles | 3-7 |
| L3 Cache | 40-45 cycles | 15 |
| 跨槽傳輸 | ? | 20 |
| 內存 | 120-240 cycles | 60-120 |


參考文章:
[http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait/](http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait/)
### 速度
對于速度,有個形象的比喻,如果,我們把CPU的一個時鐘周期看作一秒,則:
從L1 cache讀取信息就好像是拿起桌上的一張草稿紙(3秒);
從L2 cache讀取信息就好像是從身邊的書架上取出一本書(14秒);
從主存中讀取信息則相當于走到辦公樓下去買個零食(4分鐘);
硬盤尋道的時間相當于離開辦公大樓并開始長達一年零三個月的環球旅行。
### 緩存
緩存無處不在,不論是硬盤、網卡、顯卡、Raid卡、HBA卡,都有緩存;緩存是比較容易的解決性能問題的簡單方案,非常奏效,非常管用。
通過一級一級的緩存,來增加速度,比如:
CPU的L1只有512K,L2是2M,L3只有好的服務器才有,是18M,L3很貴;
硬盤的緩存,一般只有64M,通過這個64M的緩存來增加速度。
**引申一個硬盤的問題:**
如果掉電了呢?
服務器硬盤,可以通過硬盤內部電池保證緩存內容可以寫到盤片上去,而家用計算機硬盤是做不到的,這也是價格差別很大的原因之一。
擴展一下,EMS磁盤陣列,做的很好,很快,是通過多磁頭技術,減輕某一個磁盤的壓力,內部也有緩存。
關于Cache,在架構設計時,經常要解決的一個問題就是:讀和寫的不安全感。寫的時候要保證持久化設備和Cache都寫了,讀的時候,只讀Cache。而服務器端的設備,比如上面說的服務器硬盤,這些問題都是考慮到了的。
### CAS
CAS,Campare And Swap,所有的現代CPU都支持。也是各種語言中,無鎖算法,無鎖并發實現的根基。
這是CPU硬件級別的,OS也不知道,所以這種實現方式能帶來性能上的一定提升,也有副作用。
CAS提供了在激烈爭用情況下更佳的性能,換句話說,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上。
但是,這是給高級用戶準備的,慎用,只有當深切了解確實是需要的場景時,才可用。
參考文章:
[JDK 5.0 中更靈活、更具可伸縮性的鎖定機制](http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html)
# Java并發
用一個例程,來說明Java并發的問題,演示4種場景:線程不安全、synchronized、模擬CAS、原子類,如下:
~~~
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import sun.misc.Unsafe;
/**
* 演示Java并發的幾種實現方法
*
*/
public class CompareAndSwapShow {
private static int factorialUnSafe;
private static int factorialSafe;
private static int factorialCas;
private static long factorialCasOffset;
private static AtomicInteger factorialAtomic = new AtomicInteger(0);
private static int SIZE = 200;
private static CountDownLatch latch = new CountDownLatch(SIZE * 4);
private static Object lock = new Object();
private static Unsafe unsafe;
// 獲取CasTest的靜態Field的內存偏移量
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
factorialCasOffset = unsafe.staticFieldOffset(CompareAndSwapShow.class.getDeclaredField("factorialCas"));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 存儲每種計算方法的最終結果
*/
private static Map<String, Integer> factorialMax = new HashMap<String, Integer>();
public static void main(String[] args) throws Exception {
for (int i = 0; i < SIZE; i++) {
new Thread(new IncreamUnSafe()).start();
new Thread(new IncreamSafe()).start();
new Thread(new IncreamCas()).start();
new Thread(new IncreamAtomic()).start();
}
latch.await();
System.out.println("IncreamUnsafe Result:" + factorialMax.get("unsafe"));
System.out.println("IncreamSafe Result:" + factorialMax.get("safe"));
System.out.println("IncreamCas Result:" + factorialMax.get("cas"));
System.out.println("IncreamAtomic Result:" + factorialMax.get("atomic"));
}
/**
* 非線程安全的階乘
*
*/
static class IncreamUnSafe implements Runnable {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
factorialUnSafe++;
}
recordMax("unsafe", factorialUnSafe);
latch.countDown();
}
}
/**
* 線程安全的階乘
*
*/
static class IncreamSafe implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int j = 0; j < 1000; j++) {
factorialSafe++;
}
}
recordMax("safe", factorialSafe);
latch.countDown();
}
}
/**
* 用CAS算法實現的線程安全的階乘,Java的原子類就是這么搞的,死循環,就是瘋狂的壓榨CPU
*
*/
static class IncreamCas implements Runnable {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
for (;;) {
int current = factorialCas;
int next = factorialCas + 1;
if (unsafe.compareAndSwapInt(CompareAndSwapShow.class, factorialCasOffset, current, next)) {
break;
}
}
}
recordMax("cas", factorialCas);
latch.countDown();
}
}
static class IncreamAtomic implements Runnable {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
factorialAtomic.incrementAndGet();
}
recordMax("atomic", factorialAtomic.get());
latch.countDown();
}
}
/**
* 記錄每個線程的最終結果
*
* @param key
* @param target
*/
static synchronized void recordMax(String key, int target) {
Integer value = factorialMax.get(key);
if ((value == null) || (value < target)) {
factorialMax.put(key, target);
}
}
}
~~~
**重點解釋一下場景1:**factorialUnSafe++,并不是原子的,會被分解是3條CPU指令:獲取,加1,賦值;當CPU的調度,在這3個指令中間時,就可能產生線程安全問題。
附上一張圖:

# 總結
為什么AtomicInteger的實現中,用死循環?原因是因為CPU太快了。其他一些概念,比如“自旋鎖”,也是這個道理。
性能寫到極致,是可以用上L1、L2,Java程序員是做不到了,離底層太遠。應用場景中,最大的性能瓶頸,往往都是數據庫,而不是程序。如果問題出在程序,則基本都是程序的Bug,而不是程序沒有優化到極致。比如:曾經遇到一個CPU Load逐漸到達幾百的場景,而最終的答案,是特殊情況下的線程死循環。
CPU為了提高速度,加了很多Cache,這對我們寫好程序也有影響。如果是一個CPU,就不會存在線程安全問題了嗎?依然會的,CPU調度的時候,依然可能在3條指令之間中斷。
volatile關鍵字,屏蔽了L1、L2,讓變量的結果之間寫到內存,寫的時候鎖總線,所以其他線程讀到的一定是最新的結果,實現了讀線程安全。參考文章:[聊聊并發(一)——深入分析Volatile的實現原理](http://www.infoq.com/cn/articles/ftf-java-volatile)。
ThreadLocal,這個類在Java語言中,緊緊跟隨著線程,存儲線程本身的變量,非常像CPU的L1、L2。
既然CPU那么快,為什么CPU還會經常負載報警呢?拋開應用層面的原因不說,從語言角度來說,在操作系統層面上,是有鎖的, strace跟一下,發現很多,futex,這是JVM為了保證代碼執行的順序性。雖然我的代碼沒有加鎖,實際是有的。搞c的比java快得多,這也是原因。
# 程序員的天空
編程,就跟寫作、繪畫、作曲一樣,首先是一種創造性的活動,而不是一個技術工作。
當然,對一種技術或編程語言的不斷練習和保持熟悉很重要,這其實就是在學習使用工具和技法,但它并不會讓你本質上變成一名更優秀的程序員。它只是讓你能更熟練的使用工具。
而能讓你成為更優秀的程序員的是學會如何思考問題,因為最終你是把腦子里思考出的邏輯轉換成了一系列操作計算機的指令,讓計算機遵照指令解決問題。
學習如何正確的思考——如何抽象歸納,如何組合,如何分析信息,如何自我反省——可以通過各種方式,遠非只有編程一種。
有一本書:《黑客與畫家》,可以看一遍。
# 后記
知識支離破碎,單講起來很多人能知道,如果能串起來,殊為不易,這是一個融會貫通的階段,悟道的前篇。
深度,和廣度是相得益彰的。
深度,是技術層面更需要的;廣度是管理層面更需要的。
二者相輔相成,就無敵了。
- 前言
- Java之旅--如何從草根成為技術專家
- 《深入理解Java虛擬機》學習筆記
- 《Spring3.X企業應用開發實戰》學習筆記--IoC和AOP
- 《Tomcat權威指南》第二版學習筆記
- Java之旅--多線程進階
- Java之旅--Web.xml解析
- 《Spring3.X企業應用開發實戰》學習筆記--DAO和事務
- 《Spring3.X企業應用開發實戰》學習筆記--SpringMVC
- Java之旅--定時任務(Timer、Quartz、Spring、LinuxCron)
- Spring實用功能--Profile、WebService、緩存、消息、ORM
- JDK框架簡析--java.lang包中的基礎類庫、基礎數據類型
- JDK框架簡析--java.util包中的工具類庫
- JDK框架簡析--java.io包中的輸入輸出類庫
- Java之旅--通訊
- Java之旅--XML/JSON
- Java之旅--Linux&amp;java進階(看清操作系統層面的事)
- Java之旅--硬件和Java并發(神之本源)
- Java之旅--設計模式
- jetty