內容簡介
本文比較長,主要介紹 執行緒的基本概念和意義、多執行緒程式開發需要注意的問題、創建執行緒的方式、執行緒同步、執行緒通信、執行緒的生命周期、原子類等內容,
這些內容基本都是來自《java并發編程藝術》一書,在此感謝,我是在微信讀書免費看的,所以算是白嫖了,部分原始碼的解讀是筆者自己從jdk原始碼扒下來的,
執行緒的定義與意義
執行緒的定義
- 是輕量級的行程,執行緒的創建和切換成本比行程低
- 同一行程中的多條執行緒將共享該行程中的全部系統資源,如虛擬地址空間,檔案描述符和信號處理等等
- 是作業系統能夠進行運算調度的最小單位
- java程式至少有一個執行緒main,main執行緒由JVM創建
為什么要有多執行緒
- 可以充分利用多處理器核心
- 更快的回應時間,可以將資料一致性要求不強的作業交給別的執行緒做
- 更好的編程模型,例如可以使用生產者消費者模型進行解耦
并發編程需要注意的問題
背景關系切換
cpu通過時間分片來執行任務,多個執行緒在cpu上爭搶時間片執行,執行緒切換需要保存一些狀態,再次切換回去需要恢復狀態,此為背景關系切換成本,
因此并不是執行緒越多越快,頻繁的切換會損失性能
減少背景關系切換的方法:
- 無鎖并發編程:例如把一堆資料分為幾塊,交給不同執行緒執行,避免用鎖
- 使用CAS:用自旋不用鎖可以減少執行緒競爭切換,但是可能會更加耗cpu
- 使用最少的執行緒
- 使用協程:在一個執行緒里執行多個任務
死鎖
死鎖就是執行緒之間因爭奪資源, 處理不當出現的相互等待現象
避免死鎖的方法:
- 避免一個執行緒同時獲取多個鎖
- 避免一個執行緒在鎖內同時占用多個資源,盡量保證每個鎖只占用一個資源
- 嘗試使用定時鎖,lock.tryLock(timeout)
- 對于資料庫鎖,加鎖和解鎖必須在一個資料庫連接里,否則會出現解鎖失敗的情況
資源限制
程式的執行需要資源,比如資料庫連接、帶寬,可能會由于資源的限制,多個執行緒并不是并發,而是串行,不僅無優勢,反而帶來不必要的背景關系切換損耗
常見資源限制
- 硬體資源限制
- 帶寬
- 磁盤讀寫速度
- cpu處理速度
- 軟體資源限制
- 資料庫連接數
- socket連接數
應對資源限制
- 集群化,增加資源
- 根據不同的資源限制調整程式的并發度,找到瓶頸,把瓶頸資源搞多一些,或者根據這個瓶頸調整執行緒數
創建執行緒的三種方式
廢話不說,直接上代碼
繼承Thread類
// 繼承Thread
class MyThread extends Thread {
// 重寫run方法執行任務
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 可以通過this拿到當前執行緒
System.out.println(this.getName()+"執行了"+i);
}
}
}
public class Demo_02_02_1_ThreadCreateWays {
public static void main(String[] args) {
// 先new出來,然后啟動
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 10; i++) {
// 通過Thread的靜態方法拿到當前執行緒
System.out.println(Thread.currentThread().getName()+"執行了"+i);
}
}
}
實作Runnable
// 實作Runnable介面
class MyThreadByRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 不能用this了
System.out.println(Thread.currentThread().getName() + "執行了" + i);
}
}
}
public class Demo_02_02_1_ThreadCreateWays {
public static void main(String[] args) {
// 實作Runnable介面的方式啟動執行緒
Thread thread = new Thread(new MyThreadByRunnable());
thread.start();
for (int i = 0; i < 10; i++) {
// 通過Thread的靜態方法拿到當前執行緒
System.out.println(Thread.currentThread().getName() + "執行了" + i);
}
}
}
因為Runnable是函式式介面,用lamba也可以
new Thread(() -> {
System.out.println("Runnable是函式式介面, java8也可以使用lamba");
}).start();
使用Callable和Future
// 使用Callable
class MyThreadByCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"執行了"+i);
sum+=i;
}
return sum;
}
}
public class Demo_02_02_1_ThreadCreateWays {
public static void main(String[] args) {
// 用FutureTask包一層
FutureTask<Integer> futureTask = new FutureTask<>(new MyThreadByCallable());
new Thread(futureTask).start();
try {
// 呼叫futureTask的get能拿到回傳的值
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
這是最復雜的一種方式,他可以有回傳值,歸納一下步驟:
- 搞一個類實作
Callable介面,重寫call方法,在call執行任務 - 用
FutureTask包裝實作Callable介面類的實體 - 將
FutureTask的實體作為Thread構造引數 - 呼叫
FutureTask實體的get拿到回傳值,調這一句會阻塞父執行緒
Callable也是函式式介面,所以也能用lamba
為啥Thread構造里邊能放Runnable,也能放FutureTask? 其實FutureTask繼承RunnableFuture,而RunnableFuture繼承Runnable和Future,所以FutureTask也是Runnable
三種方式比較
| 方式 | 使用簡易程度 | 是否可以共享任務代碼 | 是否可以有回傳值 | 是否可以宣告拋出例外 | 是否可以再繼承別的類 |
|---|---|---|---|---|---|
| 繼承Thread | 簡單 | 不能 | 不能 | 不能 | 不能 |
| Runnable | 中等 | 可以 | 不能 | 不能 | 可以 |
| Callable | 復雜 | 可以 | 可以 | 可以 | 可以 |
繼承Thread是最容易的,但是也是最不靈活的
使用Callable時最復雜的,但是也是最靈活的
這里說的共享任務代碼舉個例子:
還是上面那個MyThreadByRunnable類
MyThreadByRunnable myThreadByRunnable = new MyThreadByRunnable();
Thread thread = new Thread(myThreadByRunnable);
thread.start();
// 再來一個,復用了任務代碼,繼承Thread就不行
Thread thread2 = new Thread(myThreadByRunnable);
thread2.start();
執行緒的一些屬性
名字
給以給執行緒取一個響亮的名字,便于排查問題,默認為Thread-${一個數字}這個樣子
- 設定名字
threadA.setName("歡迎關注微信公號'大雄和你一起學編程'");
- 獲取名字
threadA.getName();
是否是守護執行緒(daemon)
為其他執行緒服務的執行緒可以是守護執行緒,守護執行緒的特點是如果所有的前臺執行緒死亡,則守護執行緒自動死亡,
非守護執行緒創建的執行緒默認為非守護執行緒,守護執行緒創建的則默認為守護
- set
threadA.setDaemon(true);
- get
threadA.isDaemon();
執行緒優先級(priority)
優先級高的執行緒可以得到更多cpu資源, 級別是1-10,默認優先級和創建他的父執行緒相同,main是5
set
threadA.setPriority(Thread.NORM_PRIORITY);
get
threadA.getPriority()
所屬執行緒組
可以把執行緒放到組里,一起管理
設定執行緒組
Thread的構造里邊可以指定
ThreadGroup threadGroup = new ThreadGroup("歡迎關注微信公號'大雄和你一起學編程'");
Thread thread = new Thread(threadGroup, () -> {
System.out.println("歡迎關注微信公號'大雄和你一起學編程'");
});
拿到執行緒組
thread.getThreadGroup()
基于執行緒組的操作
ThreadGroup threadGroup1 = thread.getThreadGroup();
System.out.println(threadGroup1.activeCount()); // 有多少活的執行緒
threadGroup1.interrupt(); // 中斷組里所有執行緒
threadGroup1.setMaxPriority(10); // 設定執行緒最高優先級是多少
執行緒同步
多個執行緒訪問同一個資源可能會導致結果的不確定性,因此有時需要控制只有一個執行緒訪問共享資源,此為執行緒同步,
一個是可以使用synchronized同步,一個是可以使用Lock,synchronized是也是隱式的鎖,
同步方法
class Account {
private Integer total;
public Account(int total) {
this.total = total;
}
public synchronized void draw(int money) {
if (total >= money) {
this.total = this.total - money;
System.out.println(Thread.currentThread().getName() + "剩下" + this.total);
} else {
System.out.println(Thread.currentThread().getName() + "不夠了");
}
}
public synchronized int getTotal() {
return total;
}
}
public class Demo_02_04_1_ThreadSync {
public static void main(String[] args) {
Account account = new Account(100);
Runnable runnable = new Runnable() {
@Override
public void run() {
while (account.getTotal() >= 10) {
account.draw(10);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread A = new Thread(runnable);
A.setName("A");
Thread B = new Thread(runnable);
B.setName("B");
A.start();
B.start();
}
}
假設AB兩個人從同一個賬戶里取錢,直接在draw這個方法加synchronized關鍵字,防止兩個人同時進入draw
sychronized加在普通方法上,鎖為當前實體物件
加在靜態方法上,鎖為當前類的Class
同步代碼塊
public void draw(int money) {
synchronized (total) {
if (total >= money) {
this.total = this.total - money;
System.out.println(Thread.currentThread().getName() + "剩下" + this.total);
} else {
System.out.println(Thread.currentThread().getName() + "不夠了");
}
}
}
synchronized同步塊,鎖為()里邊的物件
鎖
Lock lock = new ReentrantLock();
public void draw(int money) {
lock.lock();
try {
if (total >= money) {
this.total = this.total - money;
System.out.println(Thread.currentThread().getName() + "剩下" + this.total);
} else {
System.out.println(Thread.currentThread().getName() + "不夠了");
}
} finally {
lock.unlock();
}
}
使用比較簡單,進方法加鎖,執行完釋放,后面會專門發一篇文章介紹鎖,包括AQS之類的東西,敬請關注,
執行緒間的通信
執行緒之間協調作業的方式
基于等待通知模型的通信
等待/通知的相關方法是任意Java物件都具備的,因為這些方法被定義在java.lang.Object上,
相關API
- notify: 通知一個物件上等待的執行緒,使其從wait方法回傳,而回傳的前提是該執行緒獲取到了物件的鎖
- notifyAll: 通知物件上所有等待的執行緒,使其從wait方法回傳
- wait: 使執行緒進入WAITING(后面執行緒的生命周期里邊有)狀態,只有等待另一個執行緒通知或者被中斷才回傳,需要注意的是,呼叫wait方法后需要釋放物件的鎖
- wait(long): 和wait類似,加入了超時時間,超時了還沒被通知就直接回傳
- wait(long, int): 納秒級,不常用
一些需要注意的點:
- 使用wait()、notify()和notifyAll()時需要先對呼叫物件加鎖,
- 呼叫wait()方法后,執行緒狀態由RUNNING變為WAITING,并將當前執行緒放置到物件的等待佇列,釋放鎖,
- notify()或notifyAll()方法呼叫后,等待執行緒不會立即從wait()回傳,需要呼叫notify()或notifAll()的執行緒釋放鎖之后,等待執行緒才有機會從wait()回傳,
- notify()方法將等待佇列中的一個等待執行緒從等待佇列中移到同步佇列中,而notifyAll()方法則是將等待佇列中所有的執行緒全部移到同步佇列,被移動的執行緒狀態由WAITING變為BLOCKED,
- 從wait()方法回傳的前提是獲得了呼叫物件的鎖,
關于等待佇列和同步佇列
- 同步佇列(鎖池):假設執行緒A已經擁有了某個物件(注意:不是類)的鎖,而其它的執行緒想要呼叫這個物件的某個synchronized方法(或者synchronized塊),由于這些執行緒在進入物件的synchronized方法之前必須先獲得該物件的鎖的擁有權,但是該物件的鎖目前正被執行緒A擁有,所以這些執行緒就進入了該物件的同步佇列(鎖池)中,這些執行緒狀態為Blocked,
- 等待佇列(等待池):假設一個執行緒A呼叫了某個物件的wait()方法,執行緒A就會釋放該物件的鎖(因為wait()方法必須出現在synchronized中,這樣自然在執行wait()方法之前執行緒A就已經擁有了該物件的鎖),同時 執行緒A就進入到了該物件的等待佇列(等待池)中,此時執行緒A狀態為Waiting,如果另外的一個執行緒呼叫了相同物件的notifyAll()方法,那么 處于該物件的等待池中的執行緒就會全部進入該物件的同步佇列(鎖池)中,準備爭奪鎖的擁有權,如果另外的一個執行緒呼叫了相同物件的notify()方法,那么 僅僅有一個處于該物件的等待池中的執行緒(隨機)會進入該物件的同步佇列(鎖池),
以上來自啃碎并發(二):Java執行緒的生命周期
等待通知模型的示例
class WaitNotifyModel {
Object lock = new Object();
boolean flag = false;
public void start() {
Thread A = new Thread(() -> {
synchronized (lock) {
while (!flag) {
try {
System.out.println(Thread.currentThread().getName()+":等待通知");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+ ":收到通知,處理業務邏輯");
}
});
A.setName("我是等待者");
Thread B = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println(Thread.currentThread().getName()+":發出通知");
lock.notify();
}
});
B.setName("通知者");
A.start();
B.start();
}
}
模型歸納
等待者
synchronized (物件) {
while (不滿足條件) {
物件.wait()
}
處理業務邏輯
}
通知者
synchronized (物件) {
改變條件
物件.notify();
}
基于Condition的通信
上述的這種等待通知需要使用synchronized, 如果使用Lock的話就要用Condition了
Condition介面也提供了類似Object的監視器方法,與Lock配合可以實作等待/通知模式
Condition與Object監視器的區別
| 專案 | Object的監視器方法 | Condition |
|---|---|---|
| 前置條件 | 獲得物件的鎖 | Lock.lock()獲取鎖 Lock.newCondition()獲取Condition |
| 呼叫方式 | obj.wait() | condition.await() |
| 等待佇列個數 | 一個 | 可以多個 |
| 當前執行緒釋放鎖并進入等待狀態 | 支持 | 支持 |
| 等待狀態中不回應中斷 | 不支持 | 支持 |
| 釋放鎖進入超時等待狀態 | 支持 | 支持 |
| 進入等待狀態到將來的某個時間 | 不支持 | 支持 |
| 喚醒等待中的一個或多個執行緒 | 支持 notify notifyAll | 支持signal signalAll |
這里有一些執行緒的狀態,可以看完后邊的執行緒的生命周期再回過頭看看
示例
一般都會將Condition物件作為成員變數,當呼叫await()方法后,當前執行緒會釋放鎖并在此等待,而其他執行緒呼叫Condition物件的signal()方法,通知當前執行緒后,當前執行緒才從await()方法回傳,并且在回傳前已經獲取了鎖,
實作一個有界佇列,當佇列為空時阻塞消費執行緒,當佇列滿時阻塞生產執行緒
class BoundList<T> {
private LinkedList<T> list;
private int size;
private Lock lock = new ReentrantLock();
// 拿兩個condition,一個是非空,一個是不滿
private Condition notEmpty = lock.newCondition();
private Condition notFullCondition = lock.newCondition();
public BoundList(int size) {
this.size = size;
list = new LinkedList<>();
}
public void push(T x) throws InterruptedException {
lock.lock();
try {
while (list.size() >= size) {
// 滿了就等待
notFullCondition.await();
}
list.push(x);
// 喚醒等待的消費者
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public T get() throws InterruptedException {
lock.lock();
try {
while (list.isEmpty()) {
// 空了就等
notEmpty.await();
}
T x = list.poll();
// 喚醒生產者
notFullCondition.signalAll();
return x;
} finally {
lock.unlock();
}
}
}
public class Demo_02_05_1_Condition {
public static void main(String[] args) {
BoundList<Integer> list = new BoundList<>(10);
// 生產資料的執行緒
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
list.push(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 消費資料的執行緒
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
System.out.println(list.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
基于BlockingQueue實作執行緒通信
后面會專門發文介紹BlockingQueue, 敬請關注
控制執行緒
參考了《瘋狂java講義》的提法,將如下內容歸為控制執行緒的方式,
join
主執行緒join一個執行緒,那么主執行緒會阻塞直到join進來的執行緒執行完,主執行緒繼續執行, join如果帶超時時間的話,那么如果超時的話主執行緒也會不再等join進去的執行緒而繼續執行.
join實際就是判斷join進來的執行緒存活狀態,如果活著就呼叫wait(0),如果帶超時時間了的話,wait里邊的時間會算出來
while (isAlive()) {
wait(0);
}
API
- public final void join() throws InterruptedException
- public final synchronized void join(long millis, int nanos)
- public final synchronized void join(long millis)
例子
public class Demo_02_06_1_join extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
Demo_02_06_1_join joinThread = new Demo_02_06_1_join();
for (int i = 0; i < 100; i++) {
if (i == 10) {
joinThread.start();
joinThread.join();
}
// 打到9就停了,然后執行joinThread這里邊的代碼,完事繼續從10打
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
sleep
睡覺方法,使得執行緒暫停一段時間,進入阻塞狀態,
API
- public static native void sleep(long millis) throws InterruptedException
- public static void sleep(long millis, int nanos) throws InterruptedException
示例
public class Demo_02_06_2_sleep extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 5) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 輸出到4停止, 5秒后繼續
System.out.println(this.getName() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
Demo_02_06_2_sleep sleepThread = new Demo_02_06_2_sleep();
sleepThread.start();
}
}
yield
也是讓執行緒暫停一下,但是是進入就緒狀態,讓系統重新開始一次新的調度程序,下一次可能運氣好被yield的執行緒又被選中,
Thread.yield()
中斷
Java中斷機制是一種協作機制,也就是說通過中斷并不能直接終止另一個執行緒,而需要被中斷的執行緒自己處理中斷,
前面有一些方法宣告了InterruptedException, 這意味者他們可以被中斷,中斷后把例外拋給呼叫方,讓呼叫方自己處理.
被中斷的執行緒可以自已處理中斷,也可以不處理或者拋出去,
public class Demo_02_06_3_interrupt extends Thread {
static class MyCallable implements Callable {
@Override
public Integer call() throws InterruptedException {
for (int i = 0; i < 5000; i++) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("3333");
throw new InterruptedException("中斷我干嘛,關注 微信號 大雄和你一起學編程 呀");
}
}
return 0;
}
}
public static void main(String[] args) throws InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
for (int i = 0; i < 100; i++) {
if (i == 3) {
thread.interrupt();
}
}
try {
futureTask.get();
} catch (ExecutionException e) {
// 這里會捕獲到例外
e.printStackTrace();
}
}
}
執行緒的生命周期
啃碎并發(二):Java執行緒的生命周期 這篇文章寫的非常好,建議看一下,
要是早點發現這篇文章的話,大雄也不用費勁在《java并發編程藝術》和《瘋狂java講義》以及各種博客找資料了,
這里我只想把這篇文章里一個圖改一下貼到這里,細節部分大家可以參考上述這篇文章,
還是先說兩嘴,這個生命周期的圖我找到了不少版本,不僅圖的形式不一樣,里邊的內容也有些出入
- 《瘋狂java講義》里邊只有5中狀態,缺少WAITING和TIMED_WAITING
- 《java并發編程藝術》里邊有7中狀態
- 上邊的那篇文章,文字描述有7中狀態,但是圖里邊只有6種
大雄也懵了,遂在原始碼找到了如下一個列舉, 里面有一些注釋,翻譯了一下,
public enum State {
// 表示沒有開始的執行緒
NEW,
// 表示可運行(大家的翻譯應該是就緒)的執行緒
// 表示在JVM正在運行,但是他可能需要等作業系統分配資源
// 比如CPU
RUNNABLE,
// 表示執行緒在等待監視器鎖
// 表示正在等待監視器鎖以便重新進進入同步塊或者同步方法
// OR 在呼叫了Object.wait重新進入同步塊或者同步方法
BLOCKED,
// 呼叫如下方法之一會進入WAITING
// 1. Object.wait() 沒有加超時引數
// 2. 呼叫join() 沒有加超時引數
// 3. 呼叫LockSupport.park()
// WAITING狀態的執行緒在等待別的執行緒做一個特殊的事情(action)例如
// 1. 呼叫了wait的在等待其他執行緒呼叫notify或者notifyAll
// 2. 呼叫了join的在等待指定執行緒結束
WAITING,
// 就是有一個特定等待時間的執行緒
// 加上一個特定的正的超時時間呼叫如下方法會進入此狀態
// 1. Thread.sleep
// 2. Thread.join(long)
// 3. LockSupport.parkNanos
// 4. LockSupport.parkUntil
TIMED_WAITING,
// 執行完了結束的狀態
TERMINATED;
}
對于一個擁有8級英語水品的6級沒過的人來說,這段翻譯太難了,但是翻譯出來感覺很清晰了,
應該是 7種狀態!!!
大雄不去具體研究狀態的流轉了,直接參考一些資料及上述翻譯,搞一個前無古人、后有來者的執行緒生命周期圖

這個圖八成、沒準、大概是沒有太大問題的,此圖中,原諒色是執行緒狀態,紫色是引起狀態變化的原因,
ThereadLocal
就是系結到執行緒上邊的一個存東西的地方,
使用示例
class Profiler {
// ThreadLocal的創建
private static ThreadLocal<Long> threadLocal = new ThreadLocal<Long>(){
@Override
protected Long initialValue() {
return System.currentTimeMillis();
}
};
// 記錄開始時間
public static void begin() {
threadLocal.set(System.currentTimeMillis());
}
// 記錄耗時
public static Long end() {
return System.currentTimeMillis() - threadLocal.get();
}
}
public class Demo_02_08_1_ThreadLocal {
public static void main(String[] args) {
new Thread(() -> {
Profiler.begin();
long sum = 1;
for (int i = 1; i < 20; i++) {
sum*=i;
}
System.out.println(sum);
System.out.println(Thread.currentThread().getName()+"耗時="+Profiler.end());
}).start();
new Thread(() -> {
Profiler.begin();
int sum = 1;
for (int i = 1; i < 1000; i++) {
sum+=i;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sum);
System.out.println(Thread.currentThread().getName()+"耗時="+Profiler.end());
}).start();
}
}
InheritableThreadLocal
這種ThreadLocal可以從父執行緒傳到子執行緒,也就是子執行緒能訪問父執行緒中的InheritableThreadLocal
public class Demo_02_08_2_ThreadLocalInherit {
static class TestThreadLocalInherit extends Thread{
@Override
public void run() {
System.out.println(threadLocal.get()); // null
System.out.println(inheritableThreadLocal.get()); // 歡迎關注微信公眾號 大雄和你一起學編程
}
}
public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
public static InheritableThreadLocal<Object> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableThreadLocal.set("歡迎關注微信公眾號 大雄和你一起學編程");
threadLocal.set("ddd");
new TestThreadLocalInherit().start();
}
}
實作原理
很容易想到,因為這個東西是跟著執行緒走的,所以應該是執行緒的一個屬性,事實上也是這樣,ThreadLocal和InheritableThreadLocal都是存盤在Thread里面的,
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
上邊這個就是Thread的兩個成員變數,其實兩個是一樣的型別,
ThreadLocalMap是ThreadLocal的內部類,他里邊是一個用一個Entry陣列來存資料的,set時將ThreadLocal作為key,要存的值傳進去,他會對key做一個hash,構建Entry,放到Entry陣列里邊,
// 偽碼
static class ThreadLocalMap {
// 內部的Entry結構
static class Entry {...}
// 存資料的
private Entry[] table;
// set
private void set(ThreadLocal<?> key, Object value) {
int i = key.threadLocalHashCode & (len-1);
tab[i] = new Entry(key, value);
}
// get
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
}
再來看看ThreadLocal的get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 這個就是拿到的存在Thread的threadLocals這個變數
if (map != null) {
// 這里就是毫無難度的事情了
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 這個也很簡單,他會調你重寫的initialValue方法,拿到一個值,set進去并且回傳給你
// 這個也很有趣,一般init在初始化完成,但是他是在你取的時候去調,應該算是一個小小優化吧
return setInitialValue();
}
再來看看ThreadLocal的set, 超級簡單,不多說
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal看完了,再來瞅瞅InheritableThreadLocals,看看他是怎么可以從父執行緒那里拿東西的
// 繼承了ThreadLocal, 重寫了三個方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
// 這個方法在ThreadLocal是直接拋出一個例外UnsupportedOperationException
protected T childValue(T parentValue) {
return parentValue;
}
// 超簡單,我們的Map不要threadLocals了,改為inheritableThreadLocals
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
// 同上
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
發現他和ThreadLocal長得差不多,就是重寫了三個方法,由此看來關鍵在inheritableThreadLocals是如何傳遞的
直接在Thread里面搜inheritableThreadLocals
你會發現他是在init方法中賦值的,而init實在Thread的構造方法中呼叫的
// 這個parent就是 創建這個執行緒的那個執行緒,也就是父執行緒
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
看來現在得看看ThreadLocal.createInheritedMap這個方法了
// parentMap就是父執行緒的inheritableThreadLocals
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
// 發現很簡單,就是把父執行緒的東西到自己執行緒的inheritableThreadLocals里邊
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = https://www.cnblogs.com/floor/p/key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
總結一下
ThreadLocal和InheritableThreadLocal是基于在Thread里邊的兩個變數實作的,這兩個變數類似于一個HashMap的結構ThreadLocalMap,里邊的Entry key為ThreadLocal, value為你存的值. InheritableThreadLocal的實作主要是在執行緒創建的時候,如果父執行緒有inheritableThreadLocal, 會被拷貝到子執行緒,
原子類
一個簡單的i++操作, 多執行緒環境下如果i是共享的,這個操作就不是原子的,
為此,java.util.concurrent.atomic這個包下邊提供了一些原子類,這些原子操作類提供了一種用法簡單、性能高效、執行緒安全地更新一個變數的方式,

一個使用的例子
public class Demo_04_01_1_Atomic {
static class Counter {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public int increment() {
return atomicInteger.getAndIncrement();
}
public int get() {
return atomicInteger.get();
}
}
static class Counter2 {
private int value = https://www.cnblogs.com/floor/p/0;
public int increment() {
return value++;
}
public int get() {
return value;
}
}
public static void main(String[] args) throws InterruptedException {
// 這個用了原子類
Counter counter = new Counter();
// 這個沒有用原子類
Counter2 counter2 = new Counter2();
for (int i = 0; i < 50; i++) {
new Thread(() -> {
for (int j = 0; j < 100; j++) {
counter.increment();
counter2.increment();
}
}).start();
}
Thread.sleep(2000);
System.out.println(counter.get()); // 一定是5000
System.out.println(counter2.get()); // 可能少于5000
}
}
超級簡單~
原子類的實作沒細看,貌似是CAS吧
章小結

本圖源檔案可以在github java-concurrent-programming-art-mini對應章下面找到
參考文獻
- 啃碎并發(二):Java執行緒的生命周期
相關資源
- gitbook筆記(看起來舒服,無法打開則請訪問下邊的地址)
- gitbook筆記(沒有上一個好看)
- gitbook筆記github地址
本文是筆者閱讀《java并發編程藝術》一書的筆記中的一部分,筆者將
所有筆記已經整理成了一本gitbook電子書(還在完善中),閱讀體驗可能會好一些,若有需要可關注微信公眾號大雄和你一起學編程并在后臺回復我愛java領取,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/191476.html
標籤:Java
上一篇:Spring Boot發送郵件
下一篇:pyjsonrpc的使用
