Java 線程創(chuàng)建與常用方法
進(jìn)程與線程
進(jìn)程
- 程序由指令和數(shù)據(jù)組成,但這些指令要運(yùn)行,數(shù)據(jù)要讀寫,就必須將指令加載至 CPU,數(shù)據(jù)加載至內(nèi)存。在指令運(yùn)行過(guò)程中還需要用到磁盤、網(wǎng)絡(luò)等設(shè)備。進(jìn)程就是用來(lái)加載指令、管理內(nèi)存、管理 IO 的
- 當(dāng)一個(gè)程序被運(yùn)行,從磁盤加載這個(gè)程序的代碼至內(nèi)存,這時(shí)就開(kāi)啟了一個(gè)進(jìn)程
線程
- 一個(gè)進(jìn)程之內(nèi)可以分為一到多個(gè)線程。
- 一個(gè)線程就是一個(gè)指令流,將指令流中的一條條指令以一定的順序交給 CPU 執(zhí)行
- Java 中,線程作為最小調(diào)度單位,進(jìn)程作為資源分配的最小單位。 在 windows 中進(jìn)程是不活動(dòng)的,只是作為線程的容器
進(jìn)程與線程的區(qū)別
- 進(jìn)程基本上相互獨(dú)立的,而線程存在于進(jìn)程內(nèi),是進(jìn)程的一個(gè)子集
- 進(jìn)程擁有共享的資源,如內(nèi)存空間等,供其內(nèi)部的線程共享
- 進(jìn)程間通信較為復(fù)雜
- 同一臺(tái)計(jì)算機(jī)的進(jìn)程通信稱為 IPC(Inter-process communication)
- 不同計(jì)算機(jī)之間的進(jìn)程通信,需要通過(guò)網(wǎng)絡(luò),并遵守共同的協(xié)議,例如 HTTP
- 線程通信相對(duì)簡(jiǎn)單,因?yàn)樗鼈児蚕磉M(jìn)程內(nèi)的內(nèi)存,一個(gè)例子是多個(gè)線程可以訪問(wèn)同一個(gè)共享變量
- 線程更輕量,線程上下文切換成本一般上要比進(jìn)程上下文切換低
并行與并發(fā)
單核 cpu 下,線程實(shí)際還是 串行執(zhí)行 的。操作系統(tǒng)中有一個(gè)組件叫做任務(wù)調(diào)度器,將 cpu 的時(shí)間片(windows下時(shí)間片最小約為 15 毫秒)分給不同的程序使用,只是由于 cpu 在線程間(時(shí)間片很短)的切換非常快,人類感覺(jué)是 同時(shí)運(yùn)行的 。總結(jié)為一句話就是: 微觀串行,宏觀并行 。一般會(huì)將這種 線程輪流使用 CPU 的做法稱為并發(fā) (concurrent)
多核 cpu下,每個(gè) 核(core) 都可以調(diào)度運(yùn)行線程,這時(shí)候線程可以是并行的。
Java 線程
創(chuàng)建和運(yùn)行線程
-
直接使用 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 方式簡(jiǎn)化
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 的關(guān)系
- 用 Runnable 更容易與線程池等高級(jí) API 配合
- 用 Runnable 讓任務(wù)類脫離了 Thread 繼承體系,更靈活
線程運(yùn)行的原理
棧與棧幀
每個(gè)線程啟動(dòng)后,虛擬機(jī)就會(huì)為其分配一塊棧內(nèi)存。
- 每個(gè)棧由多個(gè)棧幀(Frame)組成,對(duì)應(yīng)著每次方法調(diào)用時(shí)所占用的內(nèi)存
- 每個(gè)線程只能有一個(gè)活動(dòng)棧幀,對(duì)應(yīng)著當(dāng)前正在執(zhí)行的那個(gè)方法
線程上下文切換
因?yàn)橐韵乱恍┰驅(qū)е?cpu 不再執(zhí)行當(dāng)前的線程,轉(zhuǎn)而執(zhí)行另一個(gè)線程的代碼
- 線程的 cpu 時(shí)間片用完
- 垃圾回收
- 有更高優(yōu)先級(jí)的線程需要運(yùn)行
- 線程自己調(diào)用了 sleep、yield、wait、join、park、synchronized、lock 等方法
當(dāng) Context Switch 發(fā)生時(shí),需要由操作系統(tǒng)保存當(dāng)前線程的狀態(tài),并恢復(fù)另一個(gè)線程的狀態(tài),Java 中對(duì)應(yīng)的概念就是程序計(jì)數(shù)器(Program Counter Register),它的作用是記住下一條 jvm 指令的執(zhí)行地址,是線程私有的
- 狀態(tài)包括程序計(jì)數(shù)器、虛擬機(jī)棧中每個(gè)棧幀的信息,如局部變量、操作數(shù)棧、返回地址等
- Context Switch 頻繁發(fā)生會(huì)影響性能
常見(jiàn)方法
| 方法名 | static | 功能說(shuō)明 | 注意 |
|---|---|---|---|
| start() | 啟動(dòng)一個(gè)新線程,在新的線程運(yùn)行 run 方法中的代碼 | start 方法只是讓線程進(jìn)入就緒,里面的代碼不一定立刻運(yùn)行(CPU的時(shí)間片還沒(méi)有分給它)。每個(gè)線程對(duì)象的 start 方法只能調(diào)用一次,否則會(huì)出現(xiàn)異常 | |
| run() | 新線程啟動(dòng)后會(huì)調(diào)用的方法 | 如果在構(gòu)造 Thread 對(duì)象時(shí)傳遞了 Runnable 參數(shù),則線程啟動(dòng)后會(huì)調(diào)用 Runnable 中的 run 方法。但可以創(chuàng)建 Thread 的子類對(duì)象來(lái)覆蓋默認(rèn)行為 | |
| join() | 等待線程運(yùn)行結(jié)束 | ||
| join(long n) | 等待線程運(yùn)行結(jié)果,最多等待 n 毫秒 | ||
| getId() | 獲取線程長(zhǎng)整型的 id | ||
| getName() | 獲取線程名 | ||
| setName(String) | 修改線程名 | ||
| getPriority() | 獲取線程優(yōu)先級(jí) | ||
| setPriority(int) | 修改線程優(yōu)先級(jí) | java中規(guī)定線程優(yōu)先級(jí)是1~10 的整數(shù),較大的優(yōu)先級(jí)能提高該線程被 CPU 調(diào)度的機(jī)率 | |
| getState() | 獲取線程狀態(tài) | Java 中線程狀態(tài)是用 6 個(gè) enum 表示,分別為:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED | |
| isInterrupted() | 判斷是否被打斷 | 不會(huì)清除 打斷標(biāo)記 | |
| isAlive() | 線程是否存活(還沒(méi)有運(yùn)行完畢) | ||
| interrupt() | 打斷線程 | 如果被打斷線程正在 sleep,wait,join 會(huì)導(dǎo)致被打斷的線程拋出 InterruptedException,并清除 打斷標(biāo)記 ;如果打斷的正在運(yùn)行的線程,則會(huì)設(shè)置 打斷標(biāo)記 ;park 的線程被打斷,也會(huì)設(shè)置 打斷標(biāo)記 | |
| interrupted() | static | 判斷當(dāng)前線程是否被打斷 | 會(huì)清除 打斷標(biāo)記 |
| currentThread() | static | 獲取當(dāng)前正在執(zhí)行的線程 | |
| sleep(long n) | static | 讓當(dāng)前執(zhí)行的線程休眠 n 毫秒,休眠時(shí)讓出 CPU 的時(shí)間片給其他程序 | |
| yield() | static | 提示線程調(diào)度器讓出當(dāng)前線程對(duì)CPU的使用 | 主要是為了測(cè)試和調(diào)試 |
start 與 run
調(diào)用 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 線程運(yùn)行, FileReader.read() 方法調(diào)用還是同步的
總結(jié)
- 直接調(diào)用 run 是在主線程中執(zhí)行了 run,沒(méi)有啟動(dòng)新的線程
- 使用 start 是啟動(dòng)新的線程,通過(guò)新的線程間接執(zhí)行 run 中的代碼
sleep 與 yield
sleep
- 調(diào)用 sleep 會(huì)讓當(dāng)前線程從 Running 進(jìn)入 Timed Waiting 狀態(tài)(阻塞)
- 其它線程可以使用 interrupt 方法打斷正在睡眠的線程,這時(shí) sleep 方法會(huì)拋出 InterruptedException
- 睡眠結(jié)束后的線程未必會(huì)立刻得到執(zhí)行(搶占時(shí)間片)
- 建議用 TimeUnit 的 sleep 代替 Thread 的 sleep 來(lái)獲得更好的可讀性
yield
- 調(diào)用 yield 會(huì)讓當(dāng)前線程從 Running 進(jìn)入 Runnable 就緒狀態(tài),然后調(diào)度執(zhí)行其它線程
- 具體的實(shí)現(xiàn)依賴于操作系統(tǒng)的任務(wù)調(diào)度器
線程優(yōu)先級(jí)
- 線程優(yōu)先級(jí)會(huì)提示(hint)調(diào)度器優(yōu)先調(diào)度該線程,但它僅僅是一個(gè)提示,調(diào)度器可以忽略它
- 如果 cpu 比較忙,那么優(yōu)先級(jí)高的線程會(huì)獲得更多的時(shí)間片,但 cpu 閑時(shí),優(yōu)先級(jí)幾乎沒(méi)作用
join
等待一個(gè)線程執(zhí)行結(jié)束
等待多個(gè)線程的結(jié)果

情況一:
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 也可以帶參數(shù),是有時(shí)效的等待。當(dāng)?shù)皆O(shè)定時(shí)間線程還未給出結(jié)果,直接向下運(yùn)行,不再等待。如果設(shè)定時(shí)間還沒(méi)到但是線程已經(jīng)執(zhí)行完畢,則直接向下執(zhí)行,不再等待。
interrupt
打斷 sleep,wait,join 的線程
這幾個(gè)方法都會(huì)讓線程進(jìn)入阻塞狀態(tài)
打斷 sleep 的線程, 會(huì)清空打斷狀態(tài),以 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等被打斷并以異常形式表現(xiàn)出來(lái)后
// 會(huì)把打斷標(biāo)記重新置為 false(未打斷狀態(tài))
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug("打斷標(biāo)記:{}",t1.isInterrupted());
}
}
輸出:
15:08:12 [t1] c.demo1 - sleep...
15:08:13 [main] c.demo1 - interrupt
15:08:13 [main] c.demo1 - 打斷標(biāo)記: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
打斷正常運(yùn)行的線程打斷標(biāo)記置為: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("被打斷了,退出循環(huán)");
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 - 被打斷了,退出循環(huán)
打斷 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("打斷狀態(tài):{}",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 - 打斷狀態(tài):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;
//啟動(dòng)監(jiān)控線程
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("執(zhí)行監(jiān)控記錄");//情況2
} catch (InterruptedException e) {
e.printStackTrace();
//重新設(shè)置打斷標(biāo)記
current.interrupt();
}
}
});
monitor.start();
}
//終止監(jiān)控線程
public void stop(){
monitor.interrupt();
}
}
輸出:
15:33:02 [Thread-0] c.TwoPhaseTermination - 執(zhí)行監(jiān)控記錄
15:33:03 [Thread-0] c.TwoPhaseTermination - 執(zhí)行監(jiān)控記錄
15:33:04 [Thread-0] c.TwoPhaseTermination - 執(zhí)行監(jiān)控記錄
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
不推薦的方法
還有一些不推薦使用的方法,這些方法已過(guò)時(shí),容易破壞同步代碼塊,造成線程死鎖
| 方法名 | static | 功能說(shuō)明 |
|---|---|---|
| stop() | 停止線程運(yùn)行 | |
| suspend() | 掛起(暫停)線程運(yùn)行 | |
| resume() | 恢復(fù)線程運(yùn)行 |

浙公網(wǎng)安備 33010602011771號(hào)