點贊關注,不再迷路,你的支持對我意義重大!
?? Hi,我是丑丑。本文 「Java 路線」| 導讀 —— 他山之石,可以攻玉 已收錄,這里有 Android 進階成長路線筆記 & 博客,歡迎跟著彭丑丑一起成長。(聯系方式在 GitHub)
前言
- 并發編程 是面試的重點,同時也是 Java 開發從入門到精通遇到的第一個坎;
- 這篇文章是 Java 并發系列的第一篇文章,為了幫助小白們漸進深入,我們先來介紹并發編程中的基礎概念。如果能幫上忙,請務必點贊加關注,這真的對我非常重要。
目錄
1. 基本概念
1.1 進程與線程
- 1、進程是操作系統進行資源分配的最小單位,而線程是 CPU 調度的最小單位;
- 2、一個進程里可以有多個線程,線程必須依賴于進程存在;
- 3、不同進程之間地址空間和資源是獨立的,同一進程中的線程共享進程的地址空間和資源,但不共享線程工作空間。
1.2 CPU 與處理器
1、根據摩爾定律,集成電路上可以容納的晶體管數目在大約每經過 24 個月便會增加一倍。但是當晶體管大小降低到一定程度會出現 「量子隧穿」,無法繼續提高晶體管的密度。因此,發展出了 單芯片多處理器技術 ,即多個處理器集成到同一個 CPU 芯片上,各個處理器處理不同的線程。所謂四核 CPU 值得就是一顆 CPU 擁有四個處理器。
2、一般情況下,一個處理器只能處理一個線程,Intel 研發的 「超線程」 技術可以使得一個處理器處理兩個線程,讓單個處理器就能使用多線程「并行」操作。
1.3 CPU 時間片輪轉機制
時間片輪轉機制(又稱 Round-Robin,RR 調度),是用于分時系統的線程調度機制,要點如下:
- 1、調度程序根據線程優先級搶占線程的 CPU 時間片;
- 2、線程結束、阻塞或者讓出時間片時,CPU 會切換執行線程(讓出時間片后依然有可能重新獲得);
- 3、時間片過長會降低響應性,時間片過短會增加過多線程切換,降低 CPU 吞吐量。
易混淆: 線程的時間片分配是搶占式的,而線程間的工作是協作式的。
1.4 并行 & 并發
并行(Parallel): 指多個 CPU 核心分別執行不同線程,兩個線程之間不搶占 CPU 資源,可以同時運行;
并發(Concurrent): 指一個 CPU 核心交替執行不同線程,從單位時間的宏觀角度看,多個線程開起來是同時運行的。(討論并發一定要約定單位時間)
1.5 并發編程注意事項
線程安全
線程死鎖
線程過多
OS限制:一個進程Linus最大線程1000 window最大線程2000
線程:棧空間(缺省1M)、文件描述符/句柄1024
- 線程饑餓
Editting...
2. Java 中的線程
2.1 執行 main() 方法,會啟動幾個線程?
總所周知,main() 方法是 Java 程序的入口,運行在主線程(線程名:main)。事實上,除了主線程外,虛擬機同時還啟動了其他子線程。我們可以通過以下代碼打印運行的線程:
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo:threadInfos) {
System.out.println("["+threadInfo.getThreadId()+"]"+" " +threadInfo.getThreadName());
}
輸出如下:
線程 | 作用 |
---|---|
[1] main | 主線程 |
[2] Reference Handler | 清除 Reference |
[3] Finalizer | 調用 finalize() 方法 |
[4] Signal Dispatcher | 分發處理發送給 JVM 信號 |
[5] Attach Listener | 內存 dump,線程 dump,類信息統計,獲取系統屬性等 |
[6] Monitor Ctrl-Break | 監控 Ctrl-Break 中斷信號 |
- [1] 主線程是程序的入口;
- [2] [3] 與引用類型和 finalize(),具體見 「Java 路線」| 引用類型 & Finalizer 機制;
- 其他幾個線程筆者不了解。
2.2 啟動線程的方式
提示: 啟動線程的方式有人認為有兩種,有人認為有三種。建議不必糾結于結論,關注是否自圓其說即可。
根據 JDK 官方在java.lang.Thread.java
中的注釋,存在 兩種啟動線程 的方式:
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread.
...
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started.
- 1、繼承 Thread 類,重寫 run(),隨后調用 Thread#start();
- 2、實現 Runnable 接口,重寫 run(),并關聯 Thread,隨后調用 Thread#start()
有的說法會將java.util.concurrent.Callable
也列為一種啟動方式,其實 Callable 只是 java.util.concurrent.FutureTask
的任務接口,而 FutureTask 本身就是 Runnable 的子類,所以「兩種方式」的說法更有說服力。
FutureTask<V> ->RunnableFuture<V> -> Runnable
Thread 和 Runnable 的區別
Thread 是 Java 線程的抽象,Runnable 是對任務的抽象,與線程沒有直接關系。Thread 可以接受任意一個 Runnable 的實例并執行。
start() 和 run() 的區別
start() 是啟動一個線程,讓一個線程進入「就緒狀態」等待分配 CPU 時間片,分到時間片后執行 run() 方法,start() 方法重復執行會拋出異常;而 run() 方法是承載業務邏輯的地方,本質上是一個普通的實例方法,可以重復執行。
2.3 線程終止的方式
- 1、自然終止
當Thread#run()
執行結束,或者執行過程拋出了未捕獲的異常,則線程 自然終止。
- 2、stop()
線程停止、暫停和恢復分別對應suspend () & resume() & stop()
,需要注意的是,這些 API 都是 過時的。
suspend()
:暫停線程,當前線程不會釋放鎖資源,而是占有鎖進入睡眠狀態,容易引發死鎖;stop()
:停止線程,沒有給予線程釋放資源的時機。3、中斷
中斷本質上不屬于終止線程的方式,但是往往 中斷會給予線程終止的時機。例如在sleep() 、wait()
等方法上阻塞的線程,就可以利用中斷來退出等待,進而判斷是否應該終止線程的業務邏輯。關于中斷的具體介紹,見「Java 路線」| 線程協作機制。
提示: 可以響應中斷的方法往往聲明
throws InterruptedException
。
3. 線程優先級
Edigging...
4. 守護線程
4.1 定義
守護(Daemon)線程是一種支持型線程,當虛擬機中所有非守護線程都終止時,虛擬機就會退出,不理會守護線程是否終止。
例如 第 2.1 節 中提到的 Reference Handler 線程和 Finalizer 線程就是守護線程。通過Thead#setDaemon(true)
可以設置守護線程,
4.2 finally{} 塊一定會執行嗎?
一般來說,try-catch-finally 代碼塊中 finally 塊是一定會執行的,除了以下特殊情況,finally{} 塊不一定會執行:
- 1、finally{} 執行之前調用
System.exit(0)
:虛擬機都推出了,finally{} 塊當然也沒機會執行了; - 2、守護線程:當虛擬機中所有非守護線程都終止時,虛擬機就會退出,這個時候守護線程中的 finally{} 塊也有可能不會執行。
掌握了并發編程的基本概念,下面我們就可以愉快地深入交流了,所有精彩內容盡在: 「Java 路線」| 導讀 —— 他山之石,可以攻玉 ,常來玩~
創作不易,你的「三連」是丑丑最大的動力,我們下次見!