前段時間讀了《Java併發編程實戰》感覺受益良多,以此博客來記錄下學習的知識點,以及感受
首先什麼是進程和線程
1.進程:計算機系統上運行的一個最小的獨立的單元
2.線程:是進程裏面運行的一個最新的單元,是cpu調度分配的最小單元
線程的生命週期
1.如圖所示
2.狀態描述
- new新建創建線程
- start是線程處於就緒狀態,等待cpu分配時間片
- run獲得cpu時間片,線程處於運行狀態
- join等待某個線程執行完畢,自己線程再執行
- sleep線程休眠一段時間自動運行
- yeild讓出時間片,等待cpu重新分配
- wait線程處於等待狀態,等待其他線程喚醒
- notify喚醒線程,使得線程處於就緒狀態
(注:wait和notify是Object的方法,推薦使用java.util.conncurren下的Condition類)
線程的創建方法
- 繼承Thread類
- 實現Runnable接口(推薦使用,便於共享同一個變量,符合面向接口編程的思想)
多線程相關的知識點
線程私有的:
pc計數器、棧內存線程共享的
堆內存、方法區
也就是共享有狀態的單例,以及類的靜態變量
所以多線程併發問題都是不安全的操作了這兩個共享變量引起的
引起不安全的線程操作的原因
- 原子性
主要就是i++的操作
1.1. 競態條件
共享變量作爲了判斷條件,比如單例模式的實現就存在競態條件
1.2. 複合操作
非原子性操作 - 可見性
工作內存:線程私有
主內存:線程共享
步驟將主內存的數據拿入棧中,直到出棧的時候寫入主內存
使得線程安全的操作
- 原子性:加鎖,採用synchronized或者lock
- 可見性:volatile,作用直接從主內存中取數據和操作數據,防止指令重排序
鎖優化技術
- 鎖範圍優化:主要是鎖的顆粒度變小,例如單例:
private CommonSingleton obj;
private CommonSingleton() {
}
public synchronized CommonSingleton newSingletonInstance() {
if (obj == null) {
obj = new CommonSingleton();
}
return obj;
}
}
鎖範圍變小的單例:
public class SafeSingleton {
private SafeSingleton obj;
private SafeSingleton() {
}
public SafeSingleton newSingletonInstance() {
if (obj == null) {
synchronized (SafeSingleton.class) {
if (obj == null) {
obj = new SafeSingleton();
}
}
}
return obj;
}
}
- 鎖分段技術:具體可以參考HashTable和ConcurrentHashMap,後這就是多個HashTable,加鎖鎖住整個對象,而是鎖住一部分
- 讀寫鎖分離:java.util.concurrent.locks.ReentrantReadWriteLock
線程之間的通信
- Object對象自帶的wait(),sleep(),notify(),yeild()
jdk1.4後支持的java.util.concurrent下的工具
Condition:負責線程的等待和喚醒
- Semaphere:控制線程執行的數量
- CyclicBarrier:達到一定數目一起執行
- CountDownLatch:降到0一起執行
- Exchanger:線程之間交換數據
線程池技術
線程池產生的原因:線程的過程主要分爲三塊,線程創建T1,線程執行T2,線程銷燬T3,由於頻繁的創建銷燬線程比較耗費時間和佔用系統的內存,所以產生了線程池,由線程池來負責T1和T3的操作,實現線程的複用
常用的線程池Executors來創建
newFixedThreadPool:固定長度的線程池,緩存隊列是無界隊列LinkedBlockingQueue
- newCachedThreadPool:無長度的線程池,沒有緩存隊列,用的是SynchronousQueue
- newSingledThreadPool:獨立的線程池,和newFixedThreadPool的區別是corePoolSize和maximumPoolSize的值都是1
- newSingleThreadScheduledExecutor:時間調度的線程池,用的是DelayedWorkQueue
線程池中的關鍵的參數
- corePoolSize:核心的池數量,也就是初始的
- maximumPoolSize:最大線程池數量
- keepalivedTime:多久時間判斷線程是否已經執行結束
- timeUnit:時間單位
- workQueue:緩存的線程隊列
- ThreadFactory:創建線程的工廠
- RejectedExecutionHandler:拒絕策略
- HashSet workers:運行的線程集
## 線程池的運行步驟 ##
首先線程池裏面默認是沒有線程的,當用戶執行execute,判斷當前運行的線程數量是否小於corePoolSize,小於則交由workers去直接創建線程,並將當前運行線程加1,如果大於了corePoolSize,則將線程放入workQueue中,等待workers裏面有線程空閒出來,則去取,如果阻塞隊列裏面的超過maximumPoolSize,則由RejectedExecutionHandler去拒絕接收線程
public static ExecutorService threadPool = new ThreadPoolExecutor(MAX_QUERY_THREAD_NUM, 200, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), new ThreadFactoryBuilder().build(),
new ThreadPoolExecutor.AbortPolicy());
注:並將線程池弄成單例,放在獨立的類裏面,防止簡歷多個線程池,造成浪費
Callable和Future
- 通過線程池的submit方法執行,與Runable不同的地方就是Callable的call方法會返回一個對象,可用take()方法去獲取,take()方法裏面可以控制時間
- CompleteService阻塞隊列,控制線程一起返回一起執行
Synchronized的實現原理
- 在java堆內存中,其實對象裏面有個對象頭的概念,對象頭裏面存儲着鎖的相關信息
- 輕量級鎖
- 偏移鎖
- 自旋鎖
- 重量級鎖