行程與執行緒
行程
- 程式由指令和資料組成,但這些指令要運行,資料要讀寫,就必須將指令加載至 CPU,資料加載至記憶體,在指令運行程序中還需要用到磁盤、網路等設備,行程就是用來加載指令、管理記憶體、管理 IO 的
- 當一個程式被運行,從磁盤加載這個程式的代碼至記憶體,這時就開啟了一個行程
執行緒
- 一個行程之內可以分為一到多個執行緒,
- 一個執行緒就是一個指令流,將指令流中的一條條指令以一定的順序交給 CPU 執行
- Java 中,執行緒作為最小調度單位,行程作為資源分配的最小單位, 在 windows 中行程是不活動的,只是作為執行緒的容器
行程與執行緒的區別
- 行程基本上相互獨立的,而執行緒存在于行程內,是行程的一個子集
- 行程擁有共享的資源,如記憶體空間等,供其內部的執行緒共享
- 行程間通信較為復雜
- 同一臺計算機的行程通信稱為 IPC(Inter-process communication)
- 不同計算機之間的行程通信,需要通過網路,并遵守共同的協議,例如 HTTP
- 執行緒通信相對簡單,因為它們共享行程內的記憶體,一個例子是多個執行緒可以訪問同一個共享變數
- 執行緒更輕量,執行緒背景關系切換成本一般上要比行程背景關系切換低
并行與并發
單核 cpu 下,執行緒實際還是 串行執行 的,作業系統中有一個組件叫做任務調度器,將 cpu 的時間片(windows下時間片最小約為 15 毫秒)分給不同的程式使用,只是由于 cpu 在執行緒間(時間片很短)的切換非常快,人類感覺是 同時運行的 ,總結為一句話就是: 微觀串行,宏觀并行 ,一般會將這種 執行緒輪流使用 CPU 的做法稱為并發 (concurrent)
多核 cpu下,每個 核(core) 都可以調度運行執行緒,這時候執行緒可以是并行的,
Java 執行緒
創建和運行執行緒
-
直接使用 Thread
package create; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "c.ThreadCre") public class ThreadCre { public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { log.debug("running"); } }; t.start(); log.debug("running"); } } -
使用 Runnable 配合 Thread
package create; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "c.RunnableCre") public class RunnableCre { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { log.debug("running"); } }; Thread t = new Thread(r,"t2"); t.start(); } }使用 lambda 方式簡化
package create; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "c.RunnableCre") public class RunnableCre { public static void main(String[] args) { Runnable r = () -> { log.debug("running"); }; Thread t = new Thread(r,"t2"); t.start(); } } -
FutureTask 配合 Thread
package create; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; @Slf4j(topic = "c.FutureTaskCre") public class FutureTaskCre { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { log.debug("running..."); Thread.sleep(1000); return 100; } }); Thread t = new Thread(task,"t1"); t.start(); log.debug("{}",task.get()); } }
Thread 與 Runnable 的關系
- 用 Runnable 更容易與執行緒池等高級 API 配合
- 用 Runnable 讓任務類脫離了 Thread 繼承體系,更靈活
執行緒運行的原理
堆疊與堆疊幀
每個執行緒啟動后,虛擬機就會為其分配一塊堆疊記憶體,
- 每個堆疊由多個堆疊幀(Frame)組成,對應著每次方法呼叫時所占用的記憶體
- 每個執行緒只能有一個活動堆疊幀,對應著當前正在執行的那個方法
執行緒背景關系切換
因為以下一些原因導致 cpu 不再執行當前的執行緒,轉而執行另一個執行緒的代碼
- 執行緒的 cpu 時間片用完
- 垃圾回收
- 有更高優先級的執行緒需要運行
- 執行緒自己呼叫了 sleep、yield、wait、join、park、synchronized、lock 等方法
當 Context Switch 發生時,需要由作業系統保存當前執行緒的狀態,并恢復另一個執行緒的狀態,Java 中對應的概念就是程式計數器(Program Counter Register),它的作用是記住下一條 jvm 指令的執行地址,是執行緒私有的
- 狀態包括程式計數器、虛擬機堆疊中每個堆疊幀的資訊,如區域變數、運算元堆疊、回傳地址等
- Context Switch 頻繁發生會影響性能
常見方法
| 方法名 | static | 功能說明 | 注意 |
|---|---|---|---|
| start() | 啟動一個新執行緒,在新的執行緒運行 run 方法中的代碼 | start 方法只是讓執行緒進入就緒,里面的代碼不一定立刻運行(CPU的時間片還沒有分給它),每個執行緒物件的 start 方法只能呼叫一次,否則會出現例外 | |
| run() | 新執行緒啟動后會呼叫的方法 | 如果在構造 Thread 物件時傳遞了 Runnable 引數,則執行緒啟動后會呼叫 Runnable 中的 run 方法,但可以創建 Thread 的子類物件來覆寫默認行為 | |
| join() | 等待執行緒運行結束 | ||
| join(long n) | 等待執行緒運行結果,最多等待 n 毫秒 | ||
| getId() | 獲取執行緒長整型的 id | ||
| getName() | 獲取執行緒名 | ||
| setName(String) | 修改執行緒名 | ||
| getPriority() | 獲取執行緒優先級 | ||
| setPriority(int) | 修改執行緒優先級 | java中規定執行緒優先級是1~10 的整數,較大的優先級能提高該執行緒被 CPU 調度的機率 | |
| getState() | 獲取執行緒狀態 | Java 中執行緒狀態是用 6 個 enum 表示,分別為:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED | |
| isInterrupted() | 判斷是否被打斷 | 不會清除 打斷標記 | |
| isAlive() | 執行緒是否存活(還沒有運行完畢) | ||
| interrupt() | 打斷執行緒 | 如果被打斷執行緒正在 sleep,wait,join 會導致被打斷的執行緒拋出 InterruptedException,并清除 打斷標記 ;如果打斷的正在運行的執行緒,則會設定 打斷標記 ;park 的執行緒被打斷,也會設定 打斷標記 | |
| interrupted() | static | 判斷當前執行緒是否被打斷 | 會清除 打斷標記 |
| currentThread() | static | 獲取當前正在執行的執行緒 | |
| sleep(long n) | static | 讓當前執行的執行緒休眠 n 毫秒,休眠時讓出 CPU 的時間片給其他程式 | |
| yield() | static | 提示執行緒調度器讓出當前執行緒對CPU的使用 | 主要是為了測驗和除錯 |
start 與 run
呼叫 run
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();
log.debug("do other things ...");
}
輸出
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...
程式仍在 main 執行緒運行, FileReader.read() 方法呼叫還是同步的
總結
- 直接呼叫 run 是在主執行緒中執行了 run,沒有啟動新的執行緒
- 使用 start 是啟動新的執行緒,通過新的執行緒間接執行 run 中的代碼
sleep 與 yield
sleep
- 呼叫 sleep 會讓當前執行緒從 Running 進入 Timed Waiting 狀態(阻塞)
- 其它執行緒可以使用 interrupt 方法打斷正在睡眠的執行緒,這時 sleep 方法會拋出 InterruptedException
- 睡眠結束后的執行緒未必會立刻得到執行(搶占時間片)
- 建議用 TimeUnit 的 sleep 代替 Thread 的 sleep 來獲得更好的可讀性
yield
- 呼叫 yield 會讓當前執行緒從 Running 進入 Runnable 就緒狀態,然后調度執行其它執行緒
- 具體的實作依賴于作業系統的任務調度器
執行緒優先級
- 執行緒優先級會提示(hint)調度器優先調度該執行緒,但它僅僅是一個提示,調度器可以忽略它
- 如果 cpu 比較忙,那么優先級高的執行緒會獲得更多的時間片,但 cpu 閑時,優先級幾乎沒作用
join
等待一個執行緒執行結束
等待多個執行緒的結果

情況一:
package testJoin;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo1")
public class demo1 {
static int r = 0 , r1 = 0 , r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
r1 = 10;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
r1 = 20;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}",r1,r2,end-start);
}
}
輸出:
14:18:02 [main] c.demo1 - join begin
14:18:03 [main] c.demo1 - t1 join end
14:18:04 [main] c.demo1 - t2 join end
14:18:04 [main] c.demo1 - r1: 20 r2: 0 cost: 2008
情況二:
package testJoin;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo1")
public class demo1 {
static int r = 0 , r1 = 0 , r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
r1 = 10;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
r1 = 20;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("join begin");
t2.join();
log.debug("t2 join end");
t1.join();
log.debug("t1 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}",r1,r2,end-start);
}
}
輸出:
14:19:19 [main] c.demo1 - join begin
14:19:21 [main] c.demo1 - t2 join end
14:19:21 [main] c.demo1 - t1 join end
14:19:21 [main] c.demo1 - r1: 20 r2: 0 cost: 2006
另外 join 也可以帶引數,是有時效的等待,當到設定時間執行緒還未給出結果,直接向下運行,不再等待,如果設定時間還沒到但是執行緒已經執行完畢,則直接向下執行,不再等待,
interrupt
打斷 sleep,wait,join 的執行緒
這幾個方法都會讓執行緒進入阻塞狀態
打斷 sleep 的執行緒, 會清空打斷狀態,以 sleep 為例
package testInterrupt;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo1")
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("sleep...");
try {
Thread.sleep(5000);
//注意:sleep,wait,join等被打斷并以例外形式表現出來后
// 會把打斷標記重新置為 false(未打斷狀態)
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug("打斷標記:{}",t1.isInterrupted());
}
}
輸出:
15:08:12 [t1] c.demo1 - sleep...
15:08:13 [main] c.demo1 - interrupt
15:08:13 [main] c.demo1 - 打斷標記:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at testInterrupt.demo1.lambda$main$0(demo1.java:11)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
打斷正常運行的執行緒打斷標記置為:true
package testInterrupt;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo2")
public class demo2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true){
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted){
log.debug("被打斷了,退出回圈");
break;
}
}
},"t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
}
輸出:
15:17:40 [main] c.demo2 - interrupt
15:17:40 [t1] c.demo2 - 被打斷了,退出回圈
打斷 park 執行緒
package testInterrupt;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.demo4")
public class demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打斷狀態:{}",Thread.currentThread().isInterrupted());
},"t1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
輸出:
14:16:21 [t1] c.demo4 - park...
14:16:22 [t1] c.demo4 - unpark...
14:16:22 [t1] c.demo4 - 打斷狀態:true
兩階段終止模式

package testInterrupt;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo3")
public class demo3 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
Thread.sleep(3500);
tpt.stop();
}
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination{
private Thread monitor;
//啟動監控執行緒
public void start(){
monitor = new Thread(() -> {
while (true){
Thread current = Thread.currentThread();
if(current.isInterrupted()){
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);//情況1
log.debug("執行監控記錄");//情況2
} catch (InterruptedException e) {
e.printStackTrace();
//重新設定打斷標記
current.interrupt();
}
}
});
monitor.start();
}
//終止監控執行緒
public void stop(){
monitor.interrupt();
}
}
輸出:
15:33:02 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
15:33:03 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
15:33:04 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at testInterrupt.TwoPhaseTermination.lambda$start$0(demo3.java:29)
at java.lang.Thread.run(Thread.java:748)
15:33:04 [Thread-0] c.TwoPhaseTermination - 料理后事
Process finished with exit code 0
不推薦的方法
還有一些不推薦使用的方法,這些方法已過時,容易破壞同步代碼塊,造成執行緒死鎖
| 方法名 | static | 功能說明 |
|---|---|---|
| stop() | 停止執行緒運行 | |
| suspend() | 掛起(暫停)執行緒運行 | |
| resume() | 恢復執行緒運行 |
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/485746.html
標籤:其他
下一篇:用代碼改變表單的外觀
